带花括号初始化器列表的已删除构造函数的默认构造

Default construction of deleted constructor with braced initializer list

假设我想禁用 class 的构造,那么我可以执行以下操作(根据 删除所有构造函数(或其他函数)的最佳样式? ):

// This results in Example being CopyConstructible:
struct Example {
  Example() = delete;
};

struct Example {
  template <typename... Ts>
  Example(Ts&&...) = delete;
};

struct Example {
  Example(const Example&) = delete;
};

第一个示例如果构造后仍然可以复制(目的是禁用),但后两个示例将禁用 几乎 所有用于创建 Example 的方法.如果我默认使用一个空的大括号初始化列表构造上述任何一个,那么该实例就会成功构造。在 Meyer 的 Effective Modern C++ 中,他给出了示例(第 51 页底部):

Widget w3{}; // calls Widget ctor with no args

因此,我预计上述 Example class 会失败,即:

Example e{};

不应构造,因为它应调用已删除的默认构造函数。但是,它确实是可用的,并且如果定义为上面的第一种情况,也是可复制的。参见 live demo。我的问题是:这是正确的吗?如果是这样,为什么?另外,如果这是正确的,我如何完全 禁用 class 的销毁?

从头开始

我们将首先阐明初始化对象和how/when/if调用构造函数的含义。
以下是我的外行对标准的解释,为了简单起见,一些不相关的细节被省略或破坏。

初始化器

初始化程序是以下之一

()     // parentheses
       // nothing
{}     // braced initializer list
= expr // assignment expression

圆括号和大括号初始化列表可能包含更多表达式。
它们是这样使用的,给定 struct S

new S()        // empty parentheses
S s(1, 2)      // parentheses with expression list as (1, 2)
S s            // nothing
S s{}          // empty braced initializer list
S s{{1}, {2}}  // braced initializer list with sublists
S s = 1        // assignment
S s = {1, 2}   // assignment with braced initializer list

注意我们还没有提到构造函数

初始化

根据使用的初始化程序执行初始化。

new S()        // value-initialize
S s(1, 2)      // direct-initialize
S s            // default-initialize
S s{}          // list-initialize
S s{{1}, {2}}  // list-initialize
S s = 1        // copy-initialize
S s = {1, 2}   // list-initialize

执行初始化后,对象被视为已初始化。

请再次注意,构造函数没有被提及

列表初始化

我们将主要解释列出初始化某物的含义,因为这是手头的问题。

当列表初始化发生时,以下被认为是顺序

  1. 如果对象是聚合类型并且列表中有一个元素是该对象的类型或从该对象的类型派生,则使用该元素初始化该对象
  2. 如果对象是聚合类型,则对象是聚合初始化的
  3. 如果列表为空,并且对象有默认构造函数,则对象被值初始化(结束调用默认构造函数)
  4. 如果对象是 class 类型,则考虑构造函数,对列表的元素执行重载解析

汇总

聚合类型定义为[dcl.init.aggr]

An aggregate is an array or a class with
-- no user-provided, explicit, or inherited constructors
-- no private or protected non-static data members
-- no virtual functions, and no virtual, private, or protected base classes

删除构造函数算作提供构造函数。

聚合元素定义为

The elements of an aggregate are:
-- for an array, the array elements in increasing subscript order, or
-- for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.

聚合初始化定义为

[...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.

Example e{}

遵循上面的规则为什么Example e{}是合法的是因为

the initializer is a braced initializer list
uses list initialization
since Example is an aggregate type
uses aggregate initialization
and therefore does not invoke any constructor

当你写Example e{}时,它不是默认构造的。它是聚合初始化的。所以,是的,它非常好。

其实下面编译

struct S
{
    S() = delete;
    S(const S&) = delete;
    S(S&&) = delete;
    S& operator=(const S&) = delete;
};

S s{};  //perfectly legal

关闭施工

确保 Example 不是聚合类型以停止聚合初始化并删除其构造函数。

这通常是微不足道的,因为大多数 classes 都有私有或受保护的数据成员。因此,人们常常忘记 C++ 中存在聚合初始化。

制作 class 非聚合的最简单方法是

struct S
{
    explicit S() = delete;
};
S s{};  //illegal, calls deleted default constructor

但是,截至2017年5月30日,只有gcc 6.1及以上版本和clang 4.0.0会拒绝这个,所有版本的CL和icc都会错误地接受这个。

其他初始化

这是 C++ 中最疯狂的角落之一,通过标准了解到底发生了什么,地狱般 提供了丰富的信息。已经写了很多references,我不会尝试解释它们。