2016-12-02 80 views
22

我想结合使用C++ 11直接数据成员初始化和“using”语法来继承基类的构造函数。现在使用gcc 5.4.0(在Ubuntu 16.04上),我观察到一个奇怪的错误,如果数据成员类型没有默认构造函数。它可能比较容易找上了以下最低例如,当明白:构造函数继承和直接成员初始化

#include <iostream> 

struct Foo { 
    Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } 
}; 

struct Base { 
    Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; } 
}; 

struct Derived : public Base { 
    using Base::Base; 
    Foo foo{42}; 
}; 

int main() { 
    Derived derived{120}; 
} 

此代码编译并与铛预期的行为执行。用gcc编译不能通过,因为编译器删除构造Derived::Derived(int)

ttt.cpp: In function ‘int main()’: 
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’ 
    Derived derived{120}; 
        ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed: 
    using Base::Base; 
      ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’ 
ttt.cpp:4:3: note: candidate: Foo::Foo(int) 
    Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } 
^
ttt.cpp:4:3: note: candidate expects 1 argument, 0 provided 
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&) 
struct Foo { 
     ^
ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided 
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&) 
ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided 

如果我添加一个默认的构造的Foo这样的:

Foo() { std::cout << "Foo::Foo()" << std::endl; }; 

也可以GCC编译它。代码的行为完全一样,特别是Foo的默认构造函数永远不会被执行。

所以我的问题是现在,这是有效的C + + 11?如果是的话,我可能已经在gcc中发现了一个bug。否则,gcc和clang都不应该给我一个错误消息,说明这是无效的C++ 11?

编辑问题后很好地回答@ vlad-from-moscow:这个bug似乎也出现在gcc 6.2中,所以我会提交一个bug报告。

2日编辑:已经有一个错误,我没有在第一搜索发现:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

+0

我在cppreference.com等网页上找不到任何关于此的内容。在那里描述了“using”语法和括号初始化符,但是没有提到关于两者结合的任何内容。 –

回答

10

的GCC不满足C++标准。 Derived类的继承构造函数应该使用为Derived继承的构造函数指定的参数,在其mem-initializer列表中调用Base构造函数。

有被写在C++标准(12.9继承构造函数)

8一类的继承的构造是隐含地,当它 是ODR使用的定义德音响(3.2)来创建它的类类型的对象(1.8)。定义继承构造函数 隐德科幻执行组的类 初始化将由用户编写 直列构造该类与MEM-初始化列表,其 只有MEM-初始化进行有MEM-初始化-id,用于命名 类中使用声明的嵌套名称指定的类, 指定在以下的表达式列表,以及其函数体中的 复合语句为空(12.6.2) 。如果该用户编写的构造函数不合格,则该程序为 不合格。表达式列表中的每个表达式的格式为 static_cast(p),其中p是相应的构造函数参数 的名称,T是p的声明类型。

也根据节(12.6。2初始化碱和成员)

8在一个非委托构造,如果一个给定的非静态数据成员 或基类不是由MEM-初始化-ID(包括 情况下指定其中存在没有MEM-初始化列表因为 具有noctor-初始化)和实体不是虚拟基类的 一个抽象类(10.4),然后

构造 - 如果实体是一个非静态数据成员有一个 括号或等于初始值设定项,实体按照 8.5中的规定进行初始化;

+0

@VladfromMoscow:问题不在于Derived(120)'不会调用'Base(120)',更多的是它错误地假设表达式列表将是'foo()'(这是无效的),尽管提供了一个初始化器,实际的调用将是'foo(42)'。如果你删除'foo'数据成员,那么gcc接受代码。 –

+0

@MatthieuM。我附上了我的答案。但是,我发现标准的各种版本之间存在矛盾。 –

5

它看起来像你说得对,有一个在GCC

的错误从§12.9[ class.inhctor]:

一个使用声明(7.3.3)名称构造隐式声明一套继承构造。从using声明名为类X继承构造的 候选集包括实际 构造函数和名义构造,从默认的参数的转换结果如下:

  • 所有非的X

模板的构造函数因此,这意味着你的Derived S级应该从其基地获得一个构造函数,它接受一个int。按照类内成员初始化的正常规则,构建Derived的实例在Foo没有默认构造函数的情况下应该不会成为问题,因为它没有被使用。因此,有一个在GCC一个错误:

§13.3.1.3初始化通过构造[over.match.ctor]

当类类型的对象是直接初始化(8.5)[...],重载解析选择构造函数。对于直接初始化,候选 函数是被初始化的对象的类的所有构造函数

所以构造函数Foo::Foo(int)应该被选中,这显然不是在gcc中。


一个问题我读这是后出现“这是否会为Derived要删除的默认构造函数?”答案是不。

便利地,所述标准提供了这个片段在下面的例子(我切除什么不需要):

struct B1 { 
    B1(int); 
}; 

struct D1 : B1 { 
    using B1::B1; 
}; 

该组中D1本构造是[强调矿]

  • D1(),隐式声明的默认构造函数,如果odr使用的话会形成错误
  • D1(const D1&),隐式声明的拷贝构造函数,而不是继承
  • D1(D1&&),隐式声明的移动构造函数,而不是继承
  • D1(int),隐式声明的继承构造
+2

这与Derived或Base的默认构造函数无关。 gcc需要Foo的默认构造函数,但它实际上从来不会调用它。 –

+0

@MartinHierholzer:你说过:“这段代码用clang编译并执行期望的行为,使用gcc它不编译,因为编译器删除了Derived的构造函数:”。这是不正确的gcc行为,以及我在这里处理的内容。你不需要再做任何事情。 – AndyG

+1

参考你的编辑:这还不是Derived的默认构造函数是否被删除。我对Derived的默认构造函数不感兴趣,它从未使用过,gcc从未抱怨过没有它。相反,gcc删除想要的构造函数Derived :: Derived(int),因为Foo的默认构造函数不存在! –