为什么在特殊成员函数中将 r 值绑定到 const 左值引用是非法的?

Arq*_*ier 8 c++ rvalue language-lawyer c++11 c++17

对于函数参数,可以将 r 值绑定到 l 值常量引用。但是,这似乎不适用于特殊成员函数,如 C++11 和 C++14 中的复制构造函数和复制赋值运算符。这有什么动机吗?

使用 C++17 时,可以从 r 值复制构造,但不能复制赋值。是否有动机为什么这里只更改了复制构造函数的行为?

所有这些都在以下示例中进行了演示:

struct B {
 B() = default;
 B(B const&) = default;
 B(B&&) = delete;
 B& operator=(B const&) = default;
 B& operator=(B&&) = delete;
};

void bar(B const &) {}

int main() {
    bar(B{}); // does work
    B(B{}); // only works in C++17

    B b{};
    b = B{}; // doesn't work
}
Run Code Online (Sandbox Code Playgroud)

son*_*yao 11

B(B{});由于强制复制省略,从 C++17 开始工作省略,,完全省略移动构造;临时对象由默认构造函数直接初始化。

(强调我的)

在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。对象被直接构建到存储中,否则它们将被复制/移动到那里。复制/移动构造函数不需要存在或可访问

注意:上面的规则没有指定优化:纯右值临时值的C++17 核心语言规范与早期的 C++ 修订版根本不同:不再有临时值可以复制/移动。描述 C++17 机制的另一种方法是“非物化值传递”:返回和使用纯右值,而不会物化临时值。

在 C++17 之前,这是一种优化并且B(B{});格式错误。

这是一种优化:即使它发生并且/move (since C++11)没有调用复制构造函数,它仍然必须存在且可访问(好像根本没有发生优化),否则程序是格式错误的

bar(B{});之所以有效,是因为只有一个重载bar将左值引用到const,右值可以绑定到该重载;这从 C++11 开始就没有改变。

b = B{};不起作用,因为选择了重载的移动赋值运算符;即使它被delete明确标记为它仍然参与重载决议[1]。对于bar如果添加超载服用右值引用作为

void bar(B&&)=delete;
Run Code Online (Sandbox Code Playgroud)

它将被选中并导致程序格式错误。


[1]请注意,对于已删除的隐式声明的移动构造函数而言,情况并非如此,重载决议会忽略这些构造函数。(C++14 起)

重载决议会忽略已删除的隐式声明的移动构造函数(否则它将阻止从右值进行复制初始化)。

  • 我想补充一点,其他示例不起作用,因为在重载解析选择正确的函数(甚至从已删除的函数中)之后应用“删除”。添加 `void bar(B&&)=delete;` 也会导致编译错误。 (2认同)