微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何创建仅调用祖父构造函数的构造函数?

如何解决如何创建仅调用祖父构造函数的构造函数?

我在层次结构中有 3 个类(称为 A、B 和 C),其中 B 扩展 A 和 C 扩展 B。类 A 有一个接受单个参数的构造函数。 C 的定义要求调用 A 的构造函数,所以我试图通过在 B 中创建一个构造函数来做到这一点。但是,编译器告诉我 C 的构造函数必须同时初始化 A 和 B。这对我来说似乎违反直觉,因为它真的应该只初始化一次。

以下代码可以更好地说明我面临的问题:

#include <iostream>

struct A {
  A(std::string name) : name_(name) {
    std::cout << "A ctor called: " << name << std::endl;
  }
  std::string name_;
};

struct B : virtual public A {
  // This constructor is required or else subclasses cannot be constructed properly
  B(std::string name) : A(name) {
    std::cout << "B ctor called: " << name << std::endl;
  }
};

struct C : virtual public B {

  // ERROR: constructor for 'C' must explicitly initialize the base class 'A' which does not have a default constructor
  // C() : B("hey") {} 

  // ERROR: constructor for 'C' must explicitly initialize the base class 'B' which does not have a default constructor
  // C() : A("hey") {} 

  // ok... but have to pass the same name twice & init'ed twice!
  C() : A("wat"),B("hey") {
    std::cout << "C ctor called" << std::endl;
  }

  // gcc reorders the constructor invocations...
  // here it's written as B then A but it would be init'ed in the order of A then B
  // C() : B("hey"),A("wat") {
  //   std::cout << "C ctor called" << std::endl;
  // }

  // ok... we can just pass a name but it's still init'ed twice!
  // C(std::string name) : B(name),A(name) {}
};

int main() {
  C c;
  std::cout << c.name_ << std::endl;
}

当我运行代码时,我得到:

A ctor called: wat
B ctor called: hey
C ctor called
wat

我的问题是:

  1. 有没有一种更简洁的方法来编写它,这样我就不必显式调用 A 和 B 的构造函数

  2. 为什么输出显示 hey 稍后设置,而 name_ 字段包含 wat(之前设置)?

解决方法

在您的解释中,您并没有说您实际上是在这样做。看到你的代码后,我去了OMG。 首先,问问自己“为什么我需要虚拟推导?”很可能没有充分的理由。 如果您认为有充分的理由,那么很可能没有。 如果您仍然坚持虚拟推导,没有充分的理由,请参阅:https://isocpp.org/wiki/faq/multiple-inheritance

,

虚拟继承的类总是由“最派生的类”继承。你的类 C 是(大部分)完全等同于:

struct C : virtual public A,virtual public B {

出于所有意图和目的,C 继承自 A,当它是最派生的类时,无论您是否显式声明它。这就是 C++ 中虚拟继承的含义。

每个类的构造函数负责构造它继承的所有类。这包括虚拟继承的类。它们是否被显式继承。即使你有:

struct C : virtual public B {

由于类 C 仍然继承自 A(无论您喜欢与否),它的所有构造函数必须正式构造A,除非A有合适的默认构造函数,在这种情况下A获得默认构造当{{ 1}} 是派生最多的类(稍后会详细介绍)。

这就是变得更加复杂的地方。假设你完成你的工作:

C

假设您现在声明其中一项:

C::C(...) : A{ ... },B{ ... } // The actual parameters are irrelevant

您成功地为“派生程度最高的类”C an_instance_of_c{ ... }; 调用了此构造函数,并且它按照您的指示乖乖地构造了 CA

现在假设您创建了继承自 BD

C

然后你继续构建一个struct D : public C { ... }

D

您刚刚发现 D::D(...) : A{ ... },B{ ... },C{ ... } 现在负责构造 DA,原因与我刚刚解释的完全相同。当然,它还负责构建 B

现在假设 C 的构造函数的参数最终调用了上面出现的相同构造函数。

好吧,猜猜看,尽管您编写了构造函数来构造 CA,但该构造函数实际上会执行其他所有操作,除了 B 现在负责构造 DA,但即使调用了相同的 B 构造函数,它也 做任何与 CA 相关的事情。毕竟,BAB 构造的。但是 D 将构建它以其他方式构建的所有其他内容。

当你有一个虚拟继承的类时,每个你显式或隐式定义的构造函数都会在你的 C++ 编译器中自动编译两个独立的代码构造函数:一种构造所有虚拟继承的类,另一种不构造。您的 C++ 编译器最终也会生成代码来调用每个构造函数的适当版本,当此类是正在构造的“最派生类”或不是时。

您偶尔会遇到虚拟继承变得口齿不清,因为它产生了所有额外的复杂性,以及由此产生的所有陷阱和陷阱(例如,即使您私下和虚拟地继承了一个类,一个子类可以始终从同一类公开虚拟继承,并且对您认为是私有继承的所有内容具有受保护和公开的访问权限)。当然,所有这些都是正确的,但是如果您完全了解虚拟继承的工作原理以及它的作用,那么使用它并没有错,并且它允许在 C++ 中完成在任何同行中无法完成的事情。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。