虚继承构造函数顺序
virtual inheritance constructor order
我正在努力更好地理解虚拟继承的概念,以及它的风险是什么。
我在另一个 post (Why is Default constructor called in virtual inheritance?) 中读到它(= 虚拟继承)改变了构造函数调用的顺序(首先调用 "grandmother",而没有虚拟继承它没有)。
所以我尝试了以下方法以了解我的想法(VS2013):
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A(){ tracefunc; }
};
struct B1 : public A
{
B1(){ tracefunc; };
};
struct B2 : virtual public A
{
B2() { tracefunc; };
};
struct C1 : public B1
{
C1() { tracefunc; };
};
struct C2 : virtual public B2
{
C2() { tracefunc; };
};
int _tmain(int argc, _TCHAR* argv[])
{
A* pa1 = new C1();
A* pa2 = new C2();
}
输出为:
A::A
B1::B1
C1::C1
A::A
B2::B2
C2::C2
这不是我所期望的(我预计 2 类 的顺序会有所不同)。
我错过了什么?有人可以解释或指导我找到解释得更好的来源吗?
谢谢!
您将无法在输出中看到任何差异,因为输出在以下任何 class 层次结构中都是相同的:
层次结构 1
class A {};
class B2 : virtual public A {};
class C2 : virtual public B2 {};
层次结构 2
class A {};
class B2 : public A {};
class C2 : virtual public B2 {};
层级 3
class A {};
class B2 : virtual public A {};
class C2 : public B2 {};
层级 3
class A {};
class B2 : public A {};
class C2 : public B2 {};
在所有这些情况下,A::A()
将首先执行,然后是B2::B2()
,然后是C2::C2()
。
它们之间的区别在于 A::A()
何时被调用。它是从 B2::B2()
还是 C2::C2()
调用的?
我不是 100% 清楚 Hiearchy 1 的答案。我认为 B2::B2()
应该从 C2::C2
调用,因为 B2
是 C
的虚拟基础 class。 A::A()
应该从 B2:B2()
调用,因为 A
是 B2
的虚拟基础 class。但我可能错了确切的顺序。
在层次结构 2 中,A::A()
将从 B2::B2()
调用。由于 B2
是 C2
的 virtual
基数 class,因此 B2::B2()
从 C2::C2()
调用。由于 A
是 B2
的正常基础 class,因此 A::A()
从 B2::B2()
调用。
在层次结构 2 中,A::A()
将从 C2::C2()
调用。由于 A
是虚拟基础 class,A::A()
从 C2::C2()
调用。 B2::B2()
在对 A::A()
的调用完成后被调用。
在层次结构 4 中,A::A()
将从 B2::B2()
调用。我认为这个案例不需要解释。
为了澄清我对 Hiearchy 1 的疑问,我使用了以下程序:
#include <iostream>
class A
{
public:
A(char const *from) { std::cout << "Called from : " << from << std::endl; }
};
class B2 : virtual public A
{
public:
B2() : A("B2::B2()") {}
};
class C2 : virtual public B2
{
public:
C2() : A("C2::C2()") {}
};
int main()
{
C2 c;
}
我得到以下输出:
Called from : C2::C2()
这证实了@T.C在他的评论中所指出的,这与我的预期不同。 A::A()
从 C2::C2
调用,而不是从 B2::B2
.
在您的示例中,您的输出是预期的。 Virtual inheritance
在实例中发挥作用,当你有一个具有多重继承的 class 时,其父 class 也继承自相同的 class/type(即 "diamond problem") .在您的示例中,您的 classes 可能被设置为虚拟继承(如果代码中的其他地方需要),但它们不一定 'virtually inherit' 基于您的示例,因为 none derived classes (B1/B2/C1/C2
) 比直接从 A
.
继承更多
为了扩展,我调整了您的示例以解释更多内容:
#include <cstdio>
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A() { tracefunc; }
virtual void write() { tracefunc; }
virtual void read() { tracefunc; }
};
struct B1 : public A
{
B1() { tracefunc; };
void read(){ tracefunc; }
};
struct C1 : public A
{
C1() { tracefunc; };
void write(){ tracefunc; }
};
struct B2 : virtual public A
{
B2() { tracefunc; };
void read(){ tracefunc; }
};
struct C2 : virtual public A
{
C2() { tracefunc; };
void write(){ tracefunc; }
};
// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any
// of the base function (i.e. A::read or A::write) from the derived class, the call is
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class.
struct Z1 : public B1, public C1
{
Z1() { tracefunc; }
};
// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous
struct Z2 : public B2, public C2
{
Z2() { tracefunc; }
};
int _tmain(int argc, _TCHAR* argv[])
{
// gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A'
Z1 z1;
// gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base
Z2 z2;
z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?)
z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?)
z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write()
z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read()
return 0;
}
虽然在 z1
变量的情况下,我们人类可能会 "obvious" 调用我们打算进行的调用,因为 B1
没有 write
方法,我会 "expect" 编译器选择 C1::write
方法,但由于对象的内存映射如何工作,它会出现问题,因为 A
的基本副本在 C1
对象的信息 (pointers/references/handles) 可能不同于 B1
对象中 A
基的副本(因为技术上有 2 个 A
基的副本) ;因此调用 B1::read() { this->write(); }
可能会产生意想不到的行为(尽管不是未定义的)。
基本 class 说明符上的 virtual
关键字明确表示其他 class 实际上继承自相同基本类型的实体只能获得该基本类型的 1 个副本.
请注意,上面的代码应该无法编译,编译器错误解释了对 z1
对象的不明确调用。如果注释掉 z1.write();
和 z1.read();
行,输出(至少对我而言)如下:
A::A
B1::B1
A::A
C1::C1
Z1::Z1
A::A
B2::B2
C2::C2
Z2::Z2
C2::write
B2::read
注意在构造 Z1
之前对 A
构造函数 (A::A
) 的 2 次调用,而 Z2
仅对 A
调用了 1 次构造函数。
我建议阅读 the following on virtual inheritance,因为它更深入地介绍了其他一些需要注意的陷阱(例如虚拟继承的 classes 需要使用初始化列表来使base class ctor 调用,或者在进行此类继承时应避免使用 C 风格的转换)。
它还稍微解释了您最初提到的 constructor/destructor 排序,更具体地说,使用多重虚拟继承时如何进行排序。
希望这可以帮助理清一些问题。
编译器的输出是正确的。其实这就是关于虚拟继承的目标。虚拟继承旨在解决多重继承中的'Diamond problem'。比如B继承A,C继承A,D继承B,C。示意图是这样的:
A
| |
B C
| |
D
所以,D有两个实例A,分别来自B和C。如果A有虚函数,那就有问题了。
例如:
struct A
{
virtual void foo(){__builtin_printf("A");}
virtual void bar(){}
};
struct B : A
{
virtual void foo(){__builtin_printf("B");}
};
struct C : A
{
virtual void bar(){}
};
struct D : B, C
{
};
int main()
{
D d;
d.foo(); // Error
}
如果我用我的xlC编译器编译并且运行:
xlC -+ a.C
报错信息是这样的:
a.C:25:7: error: member 'foo' found in multiple base classes of different types
d.foo(); // Error
^
a.C:9:18: note: member found by ambiguous name lookup
virtual void foo(){__builtin_printf("B");}
^
a.C:3:18: note: member found by ambiguous name lookup
virtual void foo(){__builtin_printf("A");}
^
1 error generated.
Error while processing a.C.
错误信息很清楚,member 'foo' found in multiple base 类 of different types.如果我们加上虚拟继承,问题就解决了。因为A的施工权归D所有,所以A的实例只有一个
回到你的代码,继承图是这样的:
A A
| |
B1 B2
| |
C1 C2
没有'diamond problem',这只是单继承。所以,构造顺序也是A->B2->C2,输出没有区别。
我正在努力更好地理解虚拟继承的概念,以及它的风险是什么。
我在另一个 post (Why is Default constructor called in virtual inheritance?) 中读到它(= 虚拟继承)改变了构造函数调用的顺序(首先调用 "grandmother",而没有虚拟继承它没有)。
所以我尝试了以下方法以了解我的想法(VS2013):
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A(){ tracefunc; }
};
struct B1 : public A
{
B1(){ tracefunc; };
};
struct B2 : virtual public A
{
B2() { tracefunc; };
};
struct C1 : public B1
{
C1() { tracefunc; };
};
struct C2 : virtual public B2
{
C2() { tracefunc; };
};
int _tmain(int argc, _TCHAR* argv[])
{
A* pa1 = new C1();
A* pa2 = new C2();
}
输出为:
A::A
B1::B1
C1::C1
A::A
B2::B2
C2::C2
这不是我所期望的(我预计 2 类 的顺序会有所不同)。
我错过了什么?有人可以解释或指导我找到解释得更好的来源吗?
谢谢!
您将无法在输出中看到任何差异,因为输出在以下任何 class 层次结构中都是相同的:
层次结构 1
class A {};
class B2 : virtual public A {};
class C2 : virtual public B2 {};
层次结构 2
class A {};
class B2 : public A {};
class C2 : virtual public B2 {};
层级 3
class A {};
class B2 : virtual public A {};
class C2 : public B2 {};
层级 3
class A {};
class B2 : public A {};
class C2 : public B2 {};
在所有这些情况下,A::A()
将首先执行,然后是B2::B2()
,然后是C2::C2()
。
它们之间的区别在于 A::A()
何时被调用。它是从 B2::B2()
还是 C2::C2()
调用的?
我不是 100% 清楚 Hiearchy 1 的答案。我认为 B2::B2()
应该从 C2::C2
调用,因为 B2
是 C
的虚拟基础 class。 A::A()
应该从 B2:B2()
调用,因为 A
是 B2
的虚拟基础 class。但我可能错了确切的顺序。
在层次结构 2 中,A::A()
将从 B2::B2()
调用。由于 B2
是 C2
的 virtual
基数 class,因此 B2::B2()
从 C2::C2()
调用。由于 A
是 B2
的正常基础 class,因此 A::A()
从 B2::B2()
调用。
在层次结构 2 中,A::A()
将从 C2::C2()
调用。由于 A
是虚拟基础 class,A::A()
从 C2::C2()
调用。 B2::B2()
在对 A::A()
的调用完成后被调用。
在层次结构 4 中,A::A()
将从 B2::B2()
调用。我认为这个案例不需要解释。
为了澄清我对 Hiearchy 1 的疑问,我使用了以下程序:
#include <iostream>
class A
{
public:
A(char const *from) { std::cout << "Called from : " << from << std::endl; }
};
class B2 : virtual public A
{
public:
B2() : A("B2::B2()") {}
};
class C2 : virtual public B2
{
public:
C2() : A("C2::C2()") {}
};
int main()
{
C2 c;
}
我得到以下输出:
Called from : C2::C2()
这证实了@T.C在他的评论中所指出的,这与我的预期不同。 A::A()
从 C2::C2
调用,而不是从 B2::B2
.
在您的示例中,您的输出是预期的。 Virtual inheritance
在实例中发挥作用,当你有一个具有多重继承的 class 时,其父 class 也继承自相同的 class/type(即 "diamond problem") .在您的示例中,您的 classes 可能被设置为虚拟继承(如果代码中的其他地方需要),但它们不一定 'virtually inherit' 基于您的示例,因为 none derived classes (B1/B2/C1/C2
) 比直接从 A
.
为了扩展,我调整了您的示例以解释更多内容:
#include <cstdio>
#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
A() { tracefunc; }
virtual void write() { tracefunc; }
virtual void read() { tracefunc; }
};
struct B1 : public A
{
B1() { tracefunc; };
void read(){ tracefunc; }
};
struct C1 : public A
{
C1() { tracefunc; };
void write(){ tracefunc; }
};
struct B2 : virtual public A
{
B2() { tracefunc; };
void read(){ tracefunc; }
};
struct C2 : virtual public A
{
C2() { tracefunc; };
void write(){ tracefunc; }
};
// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any
// of the base function (i.e. A::read or A::write) from the derived class, the call is
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class.
struct Z1 : public B1, public C1
{
Z1() { tracefunc; }
};
// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous
struct Z2 : public B2, public C2
{
Z2() { tracefunc; }
};
int _tmain(int argc, _TCHAR* argv[])
{
// gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A'
Z1 z1;
// gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base
Z2 z2;
z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?)
z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?)
z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write()
z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read()
return 0;
}
虽然在 z1
变量的情况下,我们人类可能会 "obvious" 调用我们打算进行的调用,因为 B1
没有 write
方法,我会 "expect" 编译器选择 C1::write
方法,但由于对象的内存映射如何工作,它会出现问题,因为 A
的基本副本在 C1
对象的信息 (pointers/references/handles) 可能不同于 B1
对象中 A
基的副本(因为技术上有 2 个 A
基的副本) ;因此调用 B1::read() { this->write(); }
可能会产生意想不到的行为(尽管不是未定义的)。
基本 class 说明符上的 virtual
关键字明确表示其他 class 实际上继承自相同基本类型的实体只能获得该基本类型的 1 个副本.
请注意,上面的代码应该无法编译,编译器错误解释了对 z1
对象的不明确调用。如果注释掉 z1.write();
和 z1.read();
行,输出(至少对我而言)如下:
A::A
B1::B1
A::A
C1::C1
Z1::Z1
A::A
B2::B2
C2::C2
Z2::Z2
C2::write
B2::read
注意在构造 Z1
之前对 A
构造函数 (A::A
) 的 2 次调用,而 Z2
仅对 A
调用了 1 次构造函数。
我建议阅读 the following on virtual inheritance,因为它更深入地介绍了其他一些需要注意的陷阱(例如虚拟继承的 classes 需要使用初始化列表来使base class ctor 调用,或者在进行此类继承时应避免使用 C 风格的转换)。
它还稍微解释了您最初提到的 constructor/destructor 排序,更具体地说,使用多重虚拟继承时如何进行排序。
希望这可以帮助理清一些问题。
编译器的输出是正确的。其实这就是关于虚拟继承的目标。虚拟继承旨在解决多重继承中的'Diamond problem'。比如B继承A,C继承A,D继承B,C。示意图是这样的:
A
| |
B C
| |
D
所以,D有两个实例A,分别来自B和C。如果A有虚函数,那就有问题了。
例如:
struct A
{
virtual void foo(){__builtin_printf("A");}
virtual void bar(){}
};
struct B : A
{
virtual void foo(){__builtin_printf("B");}
};
struct C : A
{
virtual void bar(){}
};
struct D : B, C
{
};
int main()
{
D d;
d.foo(); // Error
}
如果我用我的xlC编译器编译并且运行:
xlC -+ a.C
报错信息是这样的:
a.C:25:7: error: member 'foo' found in multiple base classes of different types
d.foo(); // Error
^
a.C:9:18: note: member found by ambiguous name lookup
virtual void foo(){__builtin_printf("B");}
^
a.C:3:18: note: member found by ambiguous name lookup
virtual void foo(){__builtin_printf("A");}
^
1 error generated.
Error while processing a.C.
错误信息很清楚,member 'foo' found in multiple base 类 of different types.如果我们加上虚拟继承,问题就解决了。因为A的施工权归D所有,所以A的实例只有一个
回到你的代码,继承图是这样的:
A A
| |
B1 B2
| |
C1 C2
没有'diamond problem',这只是单继承。所以,构造顺序也是A->B2->C2,输出没有区别。