具有支撑初始化列表的已删除构造函数的默认构造

时间:2017-04-29 09:46:22

标签: c++ c++11 delete-operator

假设我想禁用类的构造,然后我可以执行以下操作(根据Best style for deleting all constructors (or other function)? ):

// 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类失败,即:

Example e{};

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

1 个答案:

答案 0 :(得分:7)

从头开始

我们将首先阐明初始化对象的含义以及如何/何时/是否调用构造函数。
以下是我的外行人对标准的解释,为简单起见,一些不相关的细节已被忽略或破坏。

初始化程序

初始化程序是以下之一

()     // 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. 如果对象是类类型,则考虑构造函数,使用列表元素执行重载解析
  5. 聚合

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

      

    聚合是一个数组或具有
    的类    - 没有用户提供的,显式的或继承的构造函数
       - 没有私人或受保护的非静态数据成员
       - 没有虚函数,也没有虚拟,私有或受保护的基类

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

    聚合的元素定义为

      

    汇总的要素是:
       - 对于数组,数组元素按下标顺序增加,或者为    - 对于类,声明顺序中的直接基类,后跟非声明成员的直接非静态数据成员,按声明顺序。

    聚合初始化定义为

      

    [...]初始化列表的元素按顺序作为聚合元素的初始值设定项。

    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不是聚合类型,以停止聚合初始化并删除其构造函数。

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

    使类非聚合的最简单方法是

    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 ++中最疯狂的角落之一,通过标准来了解究竟发生了什么是 hellish 的信息。已经写了很多references,我不会尝试解释它们。