对象与 {} 的比较

问题描述 投票:0回答:1

默认构造的对象通常表示“已初始化,但未存储值”状态。例如,默认构造的

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>());
添加到课程中,但这也没有帮助。)

我的问题是:

  1. 为什么在语句
    s = {}
    中,右侧可以正确转换为默认构造的对象,但在
    s == {}
    中却不是?
  2. 为什么添加
    bool operator== (const std::initializer_list<int>());
    没有帮助?
  3. 是否有一个总体理由(除了“因为标准不允许”)为什么允许这种语法来测试空对象是不可取的?
c++ initializer-list
1个回答
0
投票

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 中添加的,仅用于

=
和函数调用参数,以便大括号可以普遍用于“初始化”或设置对象的值。 (尽管这从未真正达到预期效果。)

© www.soinside.com 2019 - 2024. All rights reserved.