【问题标题】:Constructor call hierarchy构造函数调用层次结构
【发布时间】:2013-01-05 13:26:14
【问题描述】:

我在调用类型层次结构中的构造函数的规则时遇到了麻烦。这是我的工作:

class A{
protected:
    int _i;
public:
    A(){i = 0;}
    A(int i) : _i(i){}
    virtual ~A(){}
    virtual void print(){std::cout<<i<<std::endl;}
};

class B : virtual public A{
protected:
    int _j;
public:
    B() : A(){_j = 0;}
    B(int i, int j) : A(i), _j(j){}
    virtual ~B(){}
    virtual void print(){std::cout<<i<<", "<<j<<std::endl;}
};

class C : virtual public B{
protected:
    int _k;
public:
    C() : B(){_k = 0;}
    C(int i, int j, int k} : B(i,j), _k(k){}
    virtual ~C(){}
    virtual void print(){std::cout<<i<<", "<<j<<", "<<k<<std::endl;}
};

int main(){
    C* myC = new C(1,2,3);
    myC->print();
    delete myC;
    return 0;
}

现在,我想让 new C(1,2,3) 调用 B(1,2) 的构造函数,然后依次调用构造函数 A(1) 来存储 _i=1, _j=2 , _k=3。在创建类 C 的实例 myC 时,由于某种原因我不明白,但是,第一个要调用的构造函数是 A 的标准构造函数,即 A::A();这显然会导致错误的结果,因为受保护的变量 _i 被赋值为 0。构造函数 A(1) 永远不会被调用。为什么会这样?我觉得这很反直觉。是否有某种方法可以避免显式调用类型层次结构中的所有构造函数来实现所需的行为?

感谢您的帮助!

【问题讨论】:

  • 感谢您的友好回答。所以,我想我会回到 Stroustrup 重新阅读虚拟继承的概念。似乎默认使用它是不明智的;)
  • 很多人想知道为什么默认情况下继承不是虚拟的。好吧,你自己找到了答案:)

标签: c++ constructor call hierarchy


【解决方案1】:

你真的需要virtual 继承吗? 你遇到了一个问题,因为将首先调用第一个虚拟基础 ctor,但是当从 B 继承 C 时没有指定任何内容(后者已经虚拟继承了 A,因此调用了默认值)。

一种解决方案是删除虚拟继承...如 Arne Mertz 的回答中所述。 另一个(如果你真的想要虚拟继承)是从C ctor 显式调用A

C(int i, int j, int k} : A(i), B(i,j), _k(k){}

【讨论】:

    【解决方案2】:

    当您使用虚拟继承时,最派生类必须直接调用其所有虚拟基类的构造函数。在这种情况下,C 的构造函数必须调用 BA 的构造函数。由于您只调用 B 构造函数,因此它使用默认的 A 构造函数。 B构造函数调用另一个A构造函数没有关系:因为它是一个虚拟基类,所以这个调用被忽略了。

    你有两种方法可以解决这个问题:显式调用A(int) 构造函数:

    C(int i, int j, int k} : A (i), B(i,j), _k(k){}
    

    或者使用普通继承而不是虚拟继承。

    【讨论】:

    • 最派生类没有显式调用其虚拟基类的构造函数,它可以让它们像问题中那样被隐式默认初始化。但是,无论如何,您是正确的,它始终是初始化虚拟基的最派生类。
    • @CharlesBailey 重读我的答案,你是对的:我没有正确解释(这 明确 具有误导性)。我正在修复它。
    【解决方案3】:

    这是因为您使用了虚拟继承,这仅在存在多重继承时才有意义。正常继承,一切如你所愿。

    【讨论】:

    • 如果A 是一个接口,而B 扩展了该接口,那么虚拟继承确实是唯一正确的解决方案。 (当然,如果A是一个接口,它不会有任何数据成员,所以没有用户定义的构造函数。一般虚拟继承只适用于接口。)
    • @JamesKanze,这是错误的,如果您不从具有共同祖先的 2 个以上的类进行多重继承,则不需要虚拟继承。此外,您可以在没有虚拟继承的情况下实现多个接口:例如,参见 COM。
    • 我想我不明白你对“接口”的定义。您是指 Java 类型的接口(即没有成员的抽象类)吗?但是,我在 C++ 中看到了很多不同类型的东西,可以称为 itnerface,但它们都不需要虚拟继承。当然,在某些情况下,抽象类可能会纠缠于多重继承图中,但由于它们没有成员,所以即使没有虚拟继承,vtables 不应该正常工作吗?
    • @Steed 扩展一个接口时,你不知道是否会有其他扩展,也不知道某个具体类是否要实现两个或多个扩展。所以你必须虚拟继承(除非你正在处理一个小的、封闭的层次结构,你可以在必要时轻松返回并添加虚拟继承)。
    • @ArneMertz 或多或少。这是 C++ 和 Java 中的常见模式(除了它在 C++ 中工作——你的虚函数仍然可以是私有的,使用非虚拟包装器来检查前置和后置条件)。 如果你扩展了一个接口(用另一个接口),那么你通常应该虚拟继承。
    【解决方案4】:

    为什么要声明虚拟继承?如果您从 B 类中删除 virtual 关键字: virtual public A { ... 那么您的代码将正常工作。通过声明虚拟 A,C 将直接调用 A()。如果你删除 virtual,那么 C 将不会调用 A()。

    【讨论】:

      猜你喜欢
      • 2016-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-28
      • 2019-06-16
      • 1970-01-01
      • 1970-01-01
      • 2012-05-17
      相关资源
      最近更新 更多