C++ 虚拟继承对构造函数的影响
我已经开始学习虚拟继承(以及它如何解决从具有相同父类的两个父类派生类的问题)。为了更好地理解其背后的机制,我举了一个例子:C++ 虚拟继承对构造函数的影响,c++,inheritance,multiple-inheritance,virtual-inheritance,C++,Inheritance,Multiple Inheritance,Virtual Inheritance,我已经开始学习虚拟继承(以及它如何解决从具有相同父类的两个父类派生类的问题)。为了更好地理解其背后的机制,我举了一个例子: class A { public: A(string text = "Constructor A") { cout << text << endl; } }; class B: public A { public: B(): A("A called from B") { cout << "Constructor B"
class A {
public:
A(string text = "Constructor A") { cout << text << endl; }
};
class B: public A {
public:
B(): A("A called from B") { cout << "Constructor B" << endl; }
};
class C : virtual public A {
public:
C() : A("A called from C") { cout << "Constructor C" << endl; }
};
class D : public B, public C {
public:
D() { cout << "Constructor D" << endl; }
};
困扰我的是,为什么有构造函数A“表示它不是由类B
或C
调用的。为什么在构造函数C之前没有从C调用A呢。对于后者,我知道它与类C
是虚拟派生的有关,因此我猜它不会再次调用构造函数A
,因为类A
中的对象已经形成(事实上是两次)
编辑:
大体上,我只创建一个对象类型D
int main() {
D d;
}
问题
您的代码忘记了什么,并且在D
对象中仍然有两个A
对象。您可以通过向a
添加公共int test
成员来验证此声明,并尝试以下代码。您将获得两个不同的地址:
D d;
cout << &d.B::test <<endl; // the non virtual A subobject of B
cout << &d.C::test <<endl; // the virtual A subobject of C
上面的代码片段将按预期工作,您甚至可以在不出现任何歧义错误的情况下处理d.test
编辑:精致的建筑
C++规则要求每个对象具有一个虚拟<代码>一个子对象,以提供一个构造函数,用于<代码> A<代码>。在您的情况下,
D
应该提供虚拟A
的构造
由于没有显式构造函数,D
将查找默认构造函数a
。它会找到一个,因为您为a
构造函数提供了一个默认参数。这是误导,因为它告诉你“一个构造函数”,而实际上是D
使用它
如果删除此默认参数,代码将不再编译。然后您需要这样的东西才能正确构造A
:
class A {
public:
int test;
A(string text) { cout << "A is constructed: "<<text << endl; }
};
class D : public B, public C {
public:
D() : A("Mandatory, if there is no default consructor") { cout << "Constructor D" << endl; }
};
A类{
公众:
智力测验;
(字符串文本){cout首先,由于B
非虚拟地从A
派生而来,D
最终得到两个A
子对象(就好像您根本没有使用虚拟
继承一样)
非虚拟的A
由B()
构造(因此“A从B调用”
),而虚拟的在构造时打印“构造函数A”
这是因为虚基(可能是间接基)的构造函数总是由派生最多的类(D
)的构造函数调用,而不是由任何中间基(C
)的构造函数调用
这意味着C()
中的:A(“从C调用的A”)
被忽略(因为你不是在构建一个独立的C
)。而且D()
在其成员初始值设定项列表中没有提到A
,虚拟A
是在没有任何参数的情况下构建的(就像通过:A()
)
另外,值得一提的是,所有虚拟基地都是非虚拟基地
因此,您的代码保证在从B调用A之前打印构造函数A
当一个类型有一个虚拟基时,虚拟基是从派生类型最多的构造函数构造的。因此,从D的构造函数调用“构造函数A”
更详细一点:
#include <iostream>
struct base {
base() { std::cout << "base()\n"; }
base(int) { std::cout << "base(int)\n"; }
};
struct i1 : virtual base {
i1() : base(0) { std::cout << "i1()\n"; }
};
struct i2 : virtual base {
i2() : base(1) { std::cout << "i2()\n"; }
};
struct d : i1, i2 {
};
这里的输出是
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$
请注意,i1
和i2
的构造函数没有构造基
子对象。编译器注意:基只应初始化一次,而d
的构造函数就这样做了
如果希望对基本对象进行不同的初始化,请在构造函数中写入:
d::d() : base(2) {}
将其添加到类d
中会产生以下输出:
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$
你能给我们看看你的main()
您在哪里创建这些类的实例?@Hawky当然可以,但正如我所说,我只创建了一个对象类型D,就是这样。对不起,我漏掉了这一行,这是我的错。如果您试图做的是尽可能简洁地向基类添加一组预先存在的功能,那么还可以考虑作为虚拟继承的替代方法。简单地说,您可以可以将struct FooableBase:virtualbase{…}
替换为template struct Fooable:BaseT{…}
,然后将struct FooBar:FooableBase,BarableBase{…}
替换为struct FooBar:Fooable{…}
。我发现模板方法更容易使用。@alterigel这看起来是一种非常干净的方法。我一定会看一看,谢谢。我大部分时候都意识到了这一点。但困扰我的是“构造器a”来自哪里,如果它是从类B或类C调用的,它会说“a从B/C调用”,但它只是说“构造者A”就像一个没有参数的构造函数被调用。@ DZI抱歉,这是因为默认参数。参见我的编辑另一个原因C++规则是因为虚拟继承有额外的间接级别。遵循“你不为你不使用的东西付费”的规则,C++有选择的“支付”。“用于虚拟继承的额外开销,而不是默认值。如果我理解正确,初始化将从类C开始,该类实际上是从A派生的,因此将从该类调用不带参数的构造函数。然后初始化将以正常方式继续使用X(使用参数文本调用构造函数A,然后使用构造函数X)。然后初始化将移动到Y并调用其构造函数,然后最后将调用D的构造函数?@dzi,什么是X
和Y
?@dzi否,初始化从最派生的类开始,D
。它构造所有(可能是间接的)虚拟基址(A
),然后是所有直接非虚拟基址(B
(依次构造非虚拟A
)和C
(不构造任何
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$
d::d() : base(2) {}
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$