假设我想禁用类的构造,然后我可以执行以下操作(根据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。我的问题是:这是正确的,如果是的话,为什么?另外,如果这是正确的,我如何完全禁用类的销毁?
答案 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
执行初始化后,会认为对象已初始化。
请注意,同样没有提及构造函数
我们将主要解释列出初始化内容的含义,因为这是手头的问题。
当列表初始化发生时,以下内容被视为按顺序
聚合类型定义为[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,我不会尝试解释它们。