默认构造的对象通常表示“已初始化,但未存储值”状态。例如,默认构造的
std::unique_ptr
不指向任何内容(实际上通过存储异常值 nullptr
来表示这一点)。
另一方面,默认初始化通常是通过分配
{}
来完成的。这表明人们可能想通过与 {}
进行比较来测试对象是否默认初始化,但以下代码无法编译:
struct S
{
S() = default;
bool operator== (const S&) const { return false; }
};
int main ()
{
S s = {}; // Default initialization
s = {}; // Assigning a default-initialized object
if (s == {}) // Check if 's' contains a value
;
}
得到的错误是这样的:
x.cc:12:12: error: expected primary-expression before ‘{’ token
12 | if (s == {}) // Check if 's' contains a value
| ^
(人们可能会想将
bool operator== (const std::initializer_list<int>());
添加到课程中,但这也没有帮助。)
我的问题是:
s = {}
中,右侧可以正确转换为默认构造的对象,但在s == {}
中却不是?bool operator== (const std::initializer_list<int>());
没有帮助?S s = {}; // 默认初始化
这不是默认初始化。这是带有空初始化列表的 copy-list-initialization 。对于具有默认构造函数的非聚合类(如下例所示),它解析为 value-initialization,这又是与 default-initialization 不同的初始化形式,尽管两者都会调用默认构造函数(在本案)。 C++ 的初始化规则非常复杂,如果不使用正确的术语来尝试遵循它们将会失败。
s = {}; // 分配一个默认初始化的对象
这不是语法的意思。该语法仅意味着
=
运算符的重载解析是使用 {}
作为参数完成的。
所以它(几乎)相当于
s.operator=({});
根据
operator=
重载 s
的类型,这可以具有任何含义。例如,可能只有一个带有签名 operator=(int)
的重载,在这种情况下,这相当于 s = 0;
。
但是,在大多数情况下,类只有复制/移动赋值运算符。例如
S
有一个隐式移动构造函数
operator=(S&&)
重载解析会选择这个(副本分配是更差的匹配)。当从空大括号
S&&
初始化{}
引用时,这相当于创建一个S
类型的临时对象并像通过= {}
一样初始化它。然后赋值运算符中的引用绑定到这个临时对象。
{}
中的s = {}
有点奇怪,因为(与初始化中的= {}
相反)s = {}
是一个表达式,通常表达式的操作数也是表达式。但 {}
不是一个表达式。它是它自己的语法结构,没有表达式应具有的类型和值类别。
仅对于 {}
的右侧和函数调用参数,允许使用
花括号初始化列表(例如
=
)作为操作数而不是表达式。在任何一种情况下,它们的行为都不像正常表达式那样,需要特殊的规则来处理。
可以为其他运算符添加类似的语法结构,以便它们也可以使用花括号初始化列表作为参数进行重载解析,但这根本就从未添加到语言中。它是在 C++11 中添加的,仅用于
=
和函数调用参数,以便大括号可以普遍用于“初始化”或设置对象的值。 (尽管这从未真正达到预期效果。)