gcc中的初始化列表错误?

时间:2014-11-13 16:49:32

标签: c++ c++11 gcc initializer-list

请考虑以下代码,其中B是由D通过B1B2继承的虚拟基类:

#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应该没问题。如果我纠正了这一点并使B1B2的构造函数公开而不是受保护,那么一切都会变得混乱(参见here),因为输出变为

99
0
10
Final: 10

因此,事实上,初始化B1的{​​{1}}和B2构造函数的部分仍然会被执行,甚至会更改B的值。这不应该是虚拟继承的情况。请记住,只有我改变的内容

  • xB1
  • 中公开而不是受保护的构造函数
  • B2的成员初始化列表中使用classname{}语法,而不是D

我无法相信gcc中出现这样的基本问题。但是我在我的本地机器上用clang测试它,并且在那里,所有三种情况都按预期编译和运行(即像上面的第一个例子)。如果它不是错误,有人可以指出我错过的内容吗?

编辑:我的第一次搜索无论如何都没有提起,但现在我找到了this other question,至少显示了受保护/公共错误。但是,这是gcc-4.7,所以我原本预计它会在gcc-4.8中处理。那么,我是否应该总结一下初始化列表在gcc中根本搞砸了??

2 个答案:

答案 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