请考虑以下代码,其中B
是由D
通过B1
和B2
继承的虚拟基类:
#include <iostream>
class B
{
protected:
int x;
protected:
B(int x) : x{x}{std::cout << x << std::endl;}
};
class B1 : virtual public B
{
protected:
B1() : B(0){}
};
class B2 : virtual public B
{
protected:
B2() : B(10){}
};
class D : public B1, public B2
{
public:
D() : B(99), B1(), B2() {}
void print() {std::cout << "Final: " << x << std::endl;}
};
int main() {
D d;
d.print();
return 0;
}
参见工作示例here。我在B
的构造函数中使用输出,并在D
完全构造之后跟踪正在进行的操作。当我使用g ++ - 4.8.1编译上面的例子时,一切正常。它打印
99
Final: 99
因为B
的构造函数是从最派生的类(D
)调用一次,并且还确定了x
的最终值。
现在出现了奇怪的部分:如果我改变了行
D() : B(99), B1(), B2() {}
到新的统一初始化语法,即
D() : B{99}, B1{}, B2{} {}
奇怪的事情发生了。首先,它不再编译,错误
prog.cpp: In constructor ‘D::D()’:
prog.cpp:17:5: error: ‘B1::B1()’ is protected
B1() : B(0){}
^
prog.cpp:31:27: error: within this context
D() : B{99}, B1{}, B2{} {}
(同样适用于B2
,请参阅here),因为我在派生类中使用它,所以没有意义,所以protected
应该没问题。如果我纠正了这一点并使B1
和B2
的构造函数公开而不是受保护,那么一切都会变得混乱(参见here),因为输出变为
99
0
10
Final: 10
因此,事实上,初始化B1
的{{1}}和B2
构造函数的部分仍然会被执行,甚至会更改B
的值。这不应该是虚拟继承的情况。请记住,只有我改变的内容是
x
和B1
B2
的成员初始化列表中使用classname{}
语法,而不是D
。我无法相信gcc中出现这样的基本问题。但是我在我的本地机器上用clang测试它,并且在那里,所有三种情况都按预期编译和运行(即像上面的第一个例子)。如果它不是错误,有人可以指出我错过的内容吗?
编辑:我的第一次搜索无论如何都没有提起,但现在我找到了this other question,至少显示了受保护/公共错误。但是,这是gcc-4.7,所以我原本预计它会在gcc-4.8中处理。那么,我是否应该总结一下初始化列表在gcc中根本搞砸了??
答案 0 :(得分:1)
我不知道回答这个问题是否为时已晚,但您的代码在GCC 4.9.2中编译得很好!
~$g++ -std=c++11 test.cpp
~$./a.out
99
Final: 99
~$gcc --version
gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
答案 1 :(得分:1)
关于虚拟基类构造函数的多次调用:我可以使用以下代码(使用GCC 5.1.0)重现该问题。
#include <iostream>
struct V {
V(){std::cout << "V()\n";}
};
struct A : virtual V {
A() : V{} {std::cout << "A()\n";}
};
struct B : A {
B(): V{}, A{} {std::cout << "B()\n";}
};
int main(int argc, char **argv) {
B b{};
}
这导致以下输出:
V()
V()
A()
B()
我认为这不符合C ++标准:
[class.base.init] / 7
... 每个mem-initializer执行的初始化构成一个完整表达式。 mem-initializer中的任何表达式都将作为执行初始化的full-expression的一部分进行计算。一个mem-initializer mem-initializer-id表示虚拟基类在执行任何不是派生类最多的类的构造函数时被忽略。
当A构造函数的调用更改为使用括号而不是大括号时,生成的可执行文件按预期工作,并且只调用V()一次。
我为GCC创建了一个关于此问题的错误报告:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818
编辑:我错过了已经有关于此的错误报告:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922