我试图结合使用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
答案 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)
,隐式声明的继承构造函数