显式默认构造函数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了显式默认构造函数相关的知识,希望对你有一定的参考价值。

此代码与GCC 5.X,MSVC编译良好,但GCC 6.X给出错误:

“从初始化列表转换为'a'将使用显式构造函数'a :: a()'”,clang“选择的构造函数在复制初始化中是显式的”。

删除explicit或更改为a c{}解决了这个问题,但我很好奇为什么它以这种方式工作。

class a
{
public:
    explicit a () {}
};
struct b
{
    a c;
};

int main() {
    b d{};
}
答案

b是一个aggregate。使用初始化列表初始化它时,列表中的元素将初始化聚合的前n个成员,其中n是列表中元素的数量。聚合的其余元素是copy-list-initialized。

所以在你的例子中,c将被复制列表初始化,但如果选择的构造函数是explicit,那么这是错误的,因此错误。

相关的标准报价是

[dcl.init.aggr]/3

当聚合由[dcl.init.list]中指定的初始化列表初始化时,初始化列表的元素将被视为聚合元素的初始化器。聚合的显式初始化元素确定如下: ... - 如果初始化列表是初始化列表,则聚合的显式初始化元素是聚合的前n个元素,其中n是初始化列表中的元素数。 - 否则,初始化列表必须是{},并且没有显式初始化的元素。

[dcl.init.aggr]/5

对于非联合聚合,每个不是显式初始化元素的元素都初始化如下: ... - 否则,如果元素不是引用,则从空的初始化列表([dcl.init.list])复制初始化该元素。

从空的初始化列表中复制初始化c的效果描述于

[dcl.init.list]/3

列表初始化对象或类型为T的引用定义如下: ... - 否则,如果初始化列表没有元素且T是具有默认构造函数的类类型,则对象将进行值初始化。

[dcl.init]/8

要对T类型的对象进行值初始化,意味着: ... - 如果T是一个(可能是cv限定的)类类型,没有默认构造函数([class.ctor])或者是用户提供或删除的默认构造函数,那么该对象是默认初始化的;

[dcl.init]/7

要默认初始化T类型的对象,意味着: - 如果T是(可能是cv限定的)类类型,则考虑构造函数。枚举适用的构造函数([over.match.ctor]),并通过重载决策选择初始化程序()的最佳构造函数。使用空参数列表调用如此选择的构造函数来初始化对象。

[over.match.ctor]

...对于复制初始化,候选函数是该类的所有转换构造函数。

[class.conv.ctor]/1

声明没有函数说明符explicit的构造函数指定从其参数类型(如果有)到其类的类型的转换。这样的构造函数称为转换构造函数。

在上面的示例中,a没有转换构造函数,因此重载解析失败。 [class.conv.ctor] / 2中的(非规范)示例甚至包含一个非常类似的情况

  struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
  };

  Z c = {};                       // error: copy-list-initialization

您可以通过为c提供默认成员初始值设定项来避免错误

struct b
{
    a c{};  // direct-list-initialization, explicit ctor is OK
};

以上是关于显式默认构造函数的主要内容,如果未能解决你的问题,请参考以下文章

显式默认构造函数的目的

构造函数必须显式初始化没有默认构造函数的成员

C++显式构造函数 [翻译]

C++17 中的显式默认构造函数

为啥 std::in_place_t 的构造函数默认且显式?

使用枚举和模板参数正确定义显式默认构造函数