构造函数继承和直接成员初始化

时间:2016-12-02 13:00:21

标签: c++ c++11 gcc constructor

我试图结合使用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};
}

此代码使用clang编译并执行预期的行为。使用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中发现了一个错误。否则,gcc和clang都不应该给我一个错误消息,告诉我这是无效的C ++ 11吗?

在@ vlad-from-moscow回答问题之后编辑:这个错误似乎也存在于gcc 6.2中,所以我将提交错误报告。

第二次编辑:已经有一个错误,我在第一次搜索时找不到:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

2 个答案:

答案 0 :(得分:11)

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

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

  

8当一个类的继承构造函数被隐式定义   odr-used(3.2)用于创建其类类型的对象(1.8)。一个   隐式定义的继承构造函数执行set   由用户编写的类的初始化   该类的内联构造函数,其中包含mem-initializer-list   只有mem-initializer有一个mem-initializer-id来命名基数   在using-declaration和的嵌套名称指定器中表示的类   一个表达式列表,如下面指定的,以及   函数体中的复合语句为空(12.6.2)。如果说   用户编写的构造函数将是格式错误的,程序是   病态的。表达式列表中的每个表达式都是表单   static_cast(p),其中p是对应的名称   构造函数参数和T是声明的p。

类型

另根据(12.6.2初始化基础和成员)部分

  

8在非委托构造函数中,如果是给定的非静态数据成员   或者基类不是由mem-initializer-id指定的(包括   因为构造函数没有mem-initializer-list的情况   有noctor-initializer)并且实体不是虚拟基类   抽象类(10.4),然后

     

- 如果实体是具有的实体的非静态数据成员   brace-or-equal-initializer,实体按照中的规定进行初始化   8.5;

答案 1 :(得分:5)

看起来你是对的,gcc中有一个错误

来自§12.9[class.inhctor]:

  

命名构造函数的 using-declaration (7.3.3)隐式声明了一组继承构造函数。该   来自 using-declaration 中命名的类X继承构造函数的候选集合包含实际   由默认参数转换产生的构造函数和名义构造函数如下:

     
      
  • X
  • 的所有非模板构造函数   

所以这意味着你的Derived类应该从它的基础上获得一个接受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),隐式声明的继承构造函数
  •