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

Mar*_*zer 24 c++ gcc constructor c++11

我试图使用C++ 11直接数据成员初始化和"使用"语法的组合来继承基类的构造函数.现在使用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};
}
Run Code Online (Sandbox Code Playgroud)

此代码使用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
Run Code Online (Sandbox Code Playgroud)

如果我像这样向Foo添加一个默认构造函数:

  Foo() { std::cout << "Foo::Foo()" << std::endl; };
Run Code Online (Sandbox Code Playgroud)

gcc也可以编译它.代码的行为完全相同,特别是Foo的添加默认构造函数永远不会被执行.

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

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

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

Vla*_*cow 11

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

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

8当使用odr-used(3.2)来创建类类型的对象(1.8)时,隐式定义了类的继承构造函数.隐式定义的继承构造函数执行该类的初始化集合,该集合将由用户编写的该类的内联构造函数执行,该构造函数具有mem-initializer-list,其唯一的mem-initializer具有一个mem-initializer-id,用于命名在using声明的嵌套名称指定符中表示的基类和下面指定的表达式列表,其函数体中的复合语句为空(12.6.2).如果用户编写的构造函数不正确,则程序格式不正确.表达式列表中的每个表达式的格式为static_cast(p),其中p是相应构造函数参数的名称,T是声明的p类型.

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

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

- 如果实体是具有大括号或等于初始化程序的非静态数据成员,则按照8.5中的规定初始化该实体;


And*_*dyG 5

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

来自§12.9[class.inhctor]:

命名构造函数的using声明(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;
};
Run Code Online (Sandbox Code Playgroud)

存在的构造函数集D1是[ 强调我的]

  • D1(),隐式声明的默认构造函数,如果使用odr则格式错误
  • D1(const D1&),隐式声明的复制构造函数,不是继承的
  • D1(D1&&),隐式声明的移动构造函数,不是继承的
  • D1(int),隐式声明的继承构造函数

  • 这与Derived或Base的默认构造函数无关.gcc需要一个Foo的默认构造函数,但它实际上从不调用它. (2认同)