虚拟继承对构造函数的影响
Virtual inheritance impact on constructor
我已经开始学习虚拟继承(以及它如何解决 class 派生自两个父 class 具有相同父的问题)。为了更好地理解其背后的机制,我举了一个例子:
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; }
};
我有 class A
,class B
来自 A
,class C
实际上来自 A
,class D
来自 B
和 C
。在主要我只是形成一个对象 class D
: D d;
我得到以下输出
Constructor A
A called from B
Constructor B
Constructor C
Constructor D
令我困扰的是,为什么 "Constructor A" 表明它没有被 class B
或 C
调用。还有为什么"Constructor C"之前没有"A called from C"。对于后一个,我知道它与虚拟派生的 class C
有关,所以我猜它不会再次调用 Constructor A
,因为来自 class A
的对象已经形成(实际上是两次) .
编辑:
主要我只做了一个对象类型D。
int main() {
D d;
}
问题
您的代码忘记了一些东西,您在 D
对象中仍然有两个 A
对象。您可以通过将 public int test
成员添加到 A
来验证此声明,然后尝试以下代码。您将获得两个不同的地址:
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
解决方案
所有 classes 虚拟共享 A
并且直接继承自 A
必须声明虚拟继承。所以你需要更正 class B
:
class B: virtual public A {...} // you forgot the virtual here
上面的代码片段将按预期工作,您甚至可以解决 d.test
而不会出现任何歧义错误。 Online demo
编辑:A 的精巧结构
C++ 规则要求每个具有虚拟 A
子对象的对象为 A
提供构造函数。在您的情况下,D
应该提供虚拟 A
的构造。
由于没有显式构造函数,D
将寻找默认构造函数 A
。它会找到一个,因为您为 A
构造函数提供了默认参数。它具有误导性,因为它告诉您 "A constructor" 而实际上是 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; }
};
为什么C++构造规则是这样的?因为当你有虚拟继承时,没有理由B
的A
构造是C
绘制在结构上。也不是相反。另一方面,B
和 C
都定义了如何构造它们的 A
子对象。为了解决选择正确结构的歧义,决定了这条规则。如果虚拟 class 没有默认构造函数,这可能会很痛苦。
首先,由于 B
非虚拟地从 A
派生,D
以两个 A
子对象结束(就好像你没有使用 virtual
继承)。
非虚A
由B()
构造(故"A called from B"
),虚"Constructor A"
构造时打印
这是因为虚(可能是间接)基的构造函数总是由最派生的 class(D
)的构造函数调用,而不是由任何中间基(C
).
这意味着 C()
中的 : A("A called from C")
将被忽略(因为您没有构建独立的 C
)。由于 D()
没有在其成员初始化列表中提及 A
,虚拟 A
是在没有任何参数的情况下构造的(就像 : A()
)。
此外,值得一提的是,所有虚拟基地都是 constructed before 非虚拟基地。
所以你的代码保证在 A called from B
.
之前打印 Constructor A
当一个类型有一个虚基时,虚基是从最派生类型的构造函数构造的。所以 "Constructor A" 是从 D 的构造函数中调用的。
更详细一点:
#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 {
};
现在,如果代码创建类型为 i1
的对象,i1
的默认构造函数调用 base(int)
,如所写。
但是当你创建一个d
类型的对象时,d
的构造函数负责构造base
对象。由于 d
没有默认构造函数,因此编译器会在调用 i1
和 i2
.[=30 的默认构造函数之前生成一个调用 base
默认构造函数的构造函数=]
int main() {
d d_obj;
return 0;
}
这里的输出是
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$
请注意 i1
和 i2
的构造函数没有 构造 base
子对象。编译器注意了这一点:基应该只初始化一次,d
的构造函数做到了。
如果你想对基础对象进行不同的初始化,请在构造函数中写入:
d::d() : base(2) {}
将其添加到 class d
会产生以下输出:
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$
我已经开始学习虚拟继承(以及它如何解决 class 派生自两个父 class 具有相同父的问题)。为了更好地理解其背后的机制,我举了一个例子:
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; }
};
我有 class A
,class B
来自 A
,class C
实际上来自 A
,class D
来自 B
和 C
。在主要我只是形成一个对象 class D
: D d;
我得到以下输出
Constructor A
A called from B
Constructor B
Constructor C
Constructor D
令我困扰的是,为什么 "Constructor A" 表明它没有被 class B
或 C
调用。还有为什么"Constructor C"之前没有"A called from C"。对于后一个,我知道它与虚拟派生的 class C
有关,所以我猜它不会再次调用 Constructor A
,因为来自 class A
的对象已经形成(实际上是两次) .
编辑:
主要我只做了一个对象类型D。
int main() {
D d;
}
问题
您的代码忘记了一些东西,您在 D
对象中仍然有两个 A
对象。您可以通过将 public int test
成员添加到 A
来验证此声明,然后尝试以下代码。您将获得两个不同的地址:
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
解决方案
所有 classes 虚拟共享 A
并且直接继承自 A
必须声明虚拟继承。所以你需要更正 class B
:
class B: virtual public A {...} // you forgot the virtual here
上面的代码片段将按预期工作,您甚至可以解决 d.test
而不会出现任何歧义错误。 Online demo
编辑:A 的精巧结构
C++ 规则要求每个具有虚拟 A
子对象的对象为 A
提供构造函数。在您的情况下,D
应该提供虚拟 A
的构造。
由于没有显式构造函数,D
将寻找默认构造函数 A
。它会找到一个,因为您为 A
构造函数提供了默认参数。它具有误导性,因为它告诉您 "A constructor" 而实际上是 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; }
};
为什么C++构造规则是这样的?因为当你有虚拟继承时,没有理由B
的A
构造是C
绘制在结构上。也不是相反。另一方面,B
和 C
都定义了如何构造它们的 A
子对象。为了解决选择正确结构的歧义,决定了这条规则。如果虚拟 class 没有默认构造函数,这可能会很痛苦。
首先,由于 B
非虚拟地从 A
派生,D
以两个 A
子对象结束(就好像你没有使用 virtual
继承)。
非虚A
由B()
构造(故"A called from B"
),虚"Constructor A"
构造时打印
这是因为虚(可能是间接)基的构造函数总是由最派生的 class(D
)的构造函数调用,而不是由任何中间基(C
).
这意味着 C()
中的 : A("A called from C")
将被忽略(因为您没有构建独立的 C
)。由于 D()
没有在其成员初始化列表中提及 A
,虚拟 A
是在没有任何参数的情况下构造的(就像 : A()
)。
此外,值得一提的是,所有虚拟基地都是 constructed before 非虚拟基地。
所以你的代码保证在 A called from B
.
Constructor A
当一个类型有一个虚基时,虚基是从最派生类型的构造函数构造的。所以 "Constructor A" 是从 D 的构造函数中调用的。
更详细一点:
#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 {
};
现在,如果代码创建类型为 i1
的对象,i1
的默认构造函数调用 base(int)
,如所写。
但是当你创建一个d
类型的对象时,d
的构造函数负责构造base
对象。由于 d
没有默认构造函数,因此编译器会在调用 i1
和 i2
.[=30 的默认构造函数之前生成一个调用 base
默认构造函数的构造函数=]
int main() {
d d_obj;
return 0;
}
这里的输出是
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base()
i1()
i2()
[temp]$
请注意 i1
和 i2
的构造函数没有 构造 base
子对象。编译器注意了这一点:基应该只初始化一次,d
的构造函数做到了。
如果你想对基础对象进行不同的初始化,请在构造函数中写入:
d::d() : base(2) {}
将其添加到 class d
会产生以下输出:
[temp]$ Clang++ test.cpp
[temp]$ ./a.out
base(int)
i1()
i2()
[temp]$