构造虚拟基类时的编译器行为
Posted
技术标签:
【中文标题】构造虚拟基类时的编译器行为【英文标题】:Compiler behaviour when constructing virtual base class 【发布时间】:2016-11-18 09:35:43 【问题描述】:考虑这段代码:
#include <iostream>
class A
public:
A(int s) std::cout << "A(" << s << ")\n";
;
class B1 : virtual public A
public:
B1(int s1, int s2)
: As1 std::cout << "B1(" << s1 << "," << s2 << ")\n";
;
class B2 : virtual public A
public:
B2(int s1, int s2)
: As1 std::cout << "B2(" << s1 << "," << s2 << ")\n";
;
class C1 : public B1, public B2
public:
C1() : B11,2, B23,4, A5
;
class C2 : public B1, public B2
public:
C2() : B1(1,2), B2(3,4), A5
;
int main()
std::cout << "Create c1:\n";
C1 c1;
std::cout << "\n";
std::cout << "Create c2:\n";
C2 c2;
A 类是 B1 和 B2 的虚拟基类。 C1 和 C2 类是相同的,只是 C1 使用 ...
而 C2 使用 (...)
来构造 B1 和 B2。
由于这里使用了虚继承,所以类 A 应该作为 C1 或 C2 构造的一部分来构造。
如果我使用 Microsoft VS2015 编译此代码,它会在运行时产生以下输出:
Create c1:
A(5)
B1(1,2)
B2(3,4)
Create c2:
A(5)
B1(1,2)
B2(3,4)
这正是我所期望的。
但是如果我用 GCC (6.1.0) 编译它,它会产生这个输出:
Create c1:
A(5)
A(1)
B1(1,2)
A(3)
B2(3,4)
Create c2:
A(5)
B1(1,2)
B2(3,4)
这里,A的构造函数在构造c1的时候被调用了3次,而在构造c2的时候只调用了一次。
这是 GCC 中的错误还是我误解了什么?
【问题讨论】:
您可以考虑添加常规的 C++ 标签,以获得更广泛的受众。 好的,@Jonas,我用 c++ 替换了 c++11 标签。 (我的标签不能超过 5 个。) 你也试过 Clang 吗?我 99% 确定这是一个错误。 这是个bug,好像在trunk中修复了。 老实说,如果我得到一个接受的答案只是为了将您的代码粘贴到 Wandbox 中,我会感到难过 :-)。基本上,你已经完成了所有的工作,并且做得很好。以下答案之一至少添加了更多信息。 【参考方案1】:回答我自己的问题:
显然,GCC 在这种情况下确实存在错误。使用 GCC 版本 7.0.0 编译代码会产生正确的输出行为:
Create c1:
A(5)
B1(1,2)
B2(3,4)
Create c2:
A(5)
B1(1,2)
B2(3,4)
【讨论】:
【参考方案2】:不太清楚为什么会这样,但一个类不应该是多态的(至少有一个虚函数)才能正确地从它虚拟继承吗?!
【讨论】:
用 A 多态尝试过,用 gcc 5.4.0,同样的错误。 有趣的是,gcc 的 sizeof(c1) 和 sizeof(c2) 是相同的,如果在 A 中声明了一个方法,则可以从 c1 或 c2 毫无歧义地调用它。 虚拟基类通常是多态的,作为抽象接口类。但是作为基类的属性是派生类内部的属性;类型只是派生类中的虚拟或非虚拟基类。【参考方案3】:这个错误要严重得多:
#include <iostream>
class A
public:
A(int s) std::cout << "A(" << s << ")\n"; ms = s; ;
int ms;
virtual void dummy() std::cout << "Aaaaaa!" << std::endl;;
virtual ~A() std::cout << "~A(" << ms << ")\n";;
;
class B1 : virtual public A
public:
B1(int s1, int s2)
: As1 std::cout << "B1(" << s1 << "," << s2 << ")\n"; ;
;
class B2 : virtual public A
public:
B2(int s1, int s2)
: As1 std::cout << "B2(" << s1 << "," << s2 << ")\n"; ;
;
class C1 : public B1, public B2
public:
C1() : B11,2, B23,4, A5 ;
;
class C2 : public B1, public B2
public:
C2() : B1(1,2), B2(3,4), A5 ;
;
int main()
std::cout << "Create c1:\n";
C1 c1;
std::cout << "Calling A's dummy on c1: " << std::endl;
c1.dummy();
std::cout << "Size of c1: " << sizeof(c1) << std::endl;
std::cout << "\n";
std::cout << "Create c2:\n";
C2 c2;
std::cout << "Calling A's dummy on c2: " << std::endl;
c2.dummy();
std::cout << "Size of c2: " <<sizeof(c2) << std::endl;
在 GCC 5.4.0 上编译 运行此代码会产生以下输出:
创建 c1: A(5) A(1) B1(1,2) A(3) B2(3,4) 在 c1 上调用 A 的假人: 啊啊啊! c1 的大小:32 ~A(3)创建 c2: A(5) B1(1,2) B2(3,4) 在 c2 上调用 A 的假人: 啊啊啊! c2 的大小:32 ~A(5)
【讨论】:
您在这里发现了什么新的错误?除了我在原帖中提到的错误,据我所知,您的代码完全符合预期。 bug 是一样的,你是对的。只是想指向 A 的析构函数调用。以上是关于构造虚拟基类时的编译器行为的主要内容,如果未能解决你的问题,请参考以下文章