我试图使用C++ 11直接数据成员初始化和“使用”语法继承基类的构造函数的组合。现在使用gcc 5.4.0 (在Ubuntu16.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吗?
在这个问题被莫斯科的弗拉德很好地回答后编辑:这个bug似乎也出现在gcc 6.2中,所以我会提交一个bug报告。
第二个编辑:已经有一个bug,我在第一个搜索中没有找到:bug.cgi?id=67054
发布于 2016-12-02 05:44:06
gcc不符合C++标准。派生类的继承构造函数应该使用为派生继承构造函数指定的参数调用它的mem初始化程序列表中的基构造函数。
在C++标准中编写了(12.9继承构造函数)
8.当类的继承构造函数被用于创建类类型(1.8)的对象时,它被隐式地定义为defined。隐式-de继承构造函数执行类的初始化集合,该类将由用户编写的内联构造函数执行,该类的内联构造函数具有mem初始化程序-列表,其惟一的mem-初始化器-id指定在使用-的嵌套名称-specifier中表示的基类,以及函数体中的复合语句为空。如果用户编写的构造函数格式不正确,则程序的格式不正确。表达式列表中的每个表达式都是形式static_cast( p ),其中p是对应构造函数参数的名称,T是声明的p类型。
也根据这一节(12.6.2初始化基地和成员)
8在非委托构造函数中,如果给定的非静态数据成员或基类不是由mem初始化器id指定的(包括不存在mem初始化程序列表的情况,因为构造函数具有夜行初始化程序),且实体不是抽象类的虚拟基类(10.4),则该实体不是抽象类的虚拟基类(10.4)。 -如果实体是具有大括号或等初始化器的非静态数据成员,则按8.5中指定的方式初始化该实体;
发布于 2016-12-02 05:16:29
看来你是对的,gcc身上有个小毛病
来自第12.9节class.inhctor
一个使用声明(7.3.3),它隐式地声明了一组继承构造函数。在using-声明中命名的类
X
中继承的构造函数的候选集合包括实际的构造函数和由默认参数转换产生的概念构造函数,如下所示:
X
的所有非模板构造函数因此,这意味着您的Derived
类应该从其基础上获得一个接受int
的构造函数。按照类内成员初始化的常规规则,如果没有Foo
的默认构造函数,构造Foo
实例不应该是一个问题,因为它没有被使用。因此,gcc身上有一个bug:
§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)
,隐式声明的继承构造函数https://stackoverflow.com/questions/40932844
复制相似问题