【问题标题】:How to create constructor that calls to grand parent constructor only?如何创建仅调用祖父构造函数的构造函数?
【发布时间】:2021-07-17 01:01:04
【问题描述】:

我在一个层次结构中有 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(之前设置)?

【问题讨论】:

  • 虚拟基类由最派生的对象构造(因此,在您的层次结构中,C 将首先构造 A,然后构造 B)。你有必要使用虚拟基类吗?
  • virtual 继承意味着派生最多的类必须调用构造函数。如果它们有默认构造函数,那么您不必显式调用它们。 B的构造函数在构造C时不会调用A的构造函数。
  • OK,制作struct B : public A 解决了这个问题。在这种情况下,我实际上并不需要它是虚拟的。谢谢。

标签: c++ virtual-inheritance


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    struct C : virtual public A, virtual public B {
    

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

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

    struct C : virtual public B {
    

    由于C仍然继承自A(不管你喜欢与否),它的所有构造函数必须 strong> 正式构造 A,除非 A 有合适的默认构造函数,在这种情况下,A 会得到默认构造C 是最衍生的类时(稍后会详细介绍)。

    这里变得更加复杂。假设你做你的工作:

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

    假设您现在声明以下内容之一:

    C an_instance_of_c{ ... };
    

    您成功地为“最派生类”C 调用了此构造函数,并且按照您的指示,它乖乖地构造了 AB

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

    struct D : public C { ... }
    

    然后你继续构造一个D

    D::D(...) : A{ ... }, B{ ... }, C{ ... }
    

    您刚刚发现D 现在负责构造AB,原因与我刚才解释的完全相同。当然,它还负责构造C

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

    好吧,你猜怎么着,即使你编写构造函数来构造 AB,构造函数实际上会执行它所做的一切,除了 . D 现在负责构造 AB,但即使调用了相同的 C 构造函数,它不会做任何相关的事情到AB。毕竟AB 已经D 构造。但是C 将构建它所构建的所有其他内容。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-31
      • 1970-01-01
      • 1970-01-01
      • 2011-09-30
      • 1970-01-01
      相关资源
      最近更新 更多