【问题标题】:Initialization list bug in gcc?gcc中的初始化列表错误?
【发布时间】:2015-01-10 21:43:15
【问题描述】:

考虑以下代码,其中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

因为Bs 的构造函数从最派生类 (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 的构造函数设为public 而不是protected,那么一切都会变得一团糟(参见here),因为输出变为

99
0
10
Final: 10

所以,事实上B1s和B2s构造函数中初始化B的部分仍然被执行,甚至改变了x的值。这不应该是虚拟继承的情况。请记住,我唯一改变的地方是

  • B1 和 B2 中的公共而不是受保护的构造函数
  • 在D 的成员初始化列表中使用classname{} 语法,而不是classname()。

我无法相信 gcc 中出现了这样一个基本的问题。但是我在本地机器上用 clang 对其进行了测试,所有三个案例都按预期编译和运行(即像上面的第一个示例一样)。如果这不是错误,有人可以指出我所缺少的吗?

编辑:我的第一次搜索不知何故没有提出,但现在我找到了this other question,至少显示了受保护/公共错误。然而,这是 gcc-4.7,所以我原以为它会在 gcc-4.8 中处理。那么,我是否应该得出结论,初始化列表在 gcc 中根本就搞砸了!?

【问题讨论】:

  • 这适用于 clang 3.5,但适用于 gcc 4.9.0。所以这可能是某人的编译器错误。
  • 是的,这几乎就是我在问题中所说的,只是它似乎在 gcc-4.9.0 中仍然存在!让我害怕的是,这可能会默默地导致对同一个对象的多个构造函数调用(如果所有构造函数都是公共的),违反了最基本的 OOP 原则之一……
  • 我在GCC 5.1.1 中得到完全相同的错误。
  • @Galik 和 Oguk:我在最新的 GCC 5 中遇到了这个问题和许多其他问题,{braced init} 在构造函数中。更糟糕的是,其中一些非常复杂(阅读:变化无常),使得最小的复制变得困难。即便如此,他们通常也不急于解决这些问题,因为我认为他们认为我们会弄清楚 - 并且能够 - 改为使用 (parentheses)... 而不是追逐其他红色几个小时的鲱鱼,浪费我们的时间,由于这些错误,这已经发生在我身上好几次了。真可惜。

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


【解决方案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.

【讨论】:

    【解决方案2】:

    关于虚拟基类构造函数的多次调用:我可以使用以下代码重现该问题(使用 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 中的任何表达式都被评估为执行初始化的完整表达式的一部分。一个内存初始化器 其中 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

    【讨论】:

      猜你喜欢
      • 2015-02-28
      • 1970-01-01
      • 2011-07-14
      • 2012-03-25
      • 2014-10-09
      • 1970-01-01
      • 2012-06-04
      相关资源
      最近更新 更多