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

Posted

技术标签:

【中文标题】为啥在特殊成员函数中将 r 值绑定到 const 左值引用是非法的?【英文标题】:Why is it illegal to bind an r-value to a const l-value reference in special member functions?为什么在特殊成员函数中将 r 值绑定到 const 左值引用是非法的? 【发布时间】:2020-12-03 00:31:24 【问题描述】:

对于函数参数,可以将一个右值绑定到一个左值常量引用。 但是,这似乎不适用于特殊的成员函数,例如 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

【问题讨论】:

【参考方案1】:

B(B); 从 C++17 开始工作,因为 mandatory copy elision,完全省略了移动构造;临时对象直接由默认构造函数初始化。

(强调我的)

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

...

在初始化一个对象时,当初始化表达式是一个相同类类型的prvalue(忽略cv-qualification)时 变量类型:

  T x = T(T(f())); // only one call to default constructor of T, to initialize x

注意:上述规则未指定优化:prvalues 和 temporaries 的 C++17 核心语言规范与早期 C++ 修订版的基本不同:不再有临时复制/移动从。描述 C++17 机制的另一种方式是“未实现的值传递”:纯右值被返回和使用,而无需实现临时值。

在 C++17 之前,这是一种优化,B(B); 格式不正确。

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

bar(B); 有效,因为bar 只有一个重载,它采用左值引用到const,右值可以绑定到它;这在 C++11 之后没有改变。

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

void bar(B&&)=delete;

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


[1] 请注意,deleted implicitly declared move constructors 并非如此,它们会被重载决议忽略。 (C++14 起)

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

【讨论】:

我要补充一点,其他示例不起作用,因为 delete 是在重载决议选择正确的函数之后应用的,即使是从已删除的函数中。添加void bar(B&&)=delete;也会导致编译错误。 还有更多。 “重载决议忽略定义为已删除的默认移动构造函数”(class.copy.ctor)。关于移动分配也是如此。但它在这里不适用,因为移动 ctor 不是默认的(即显式删除)。但是如果你有struct C : B ,你可以从any 右值引用中复制构造和复制赋值C(例如C c; C cc = std::move(c);。我相信C++14也是如此。跨度> B(B); 不是初始化。为什么在初始化中引用关于复制省略的“规则”? @LanguageLawyer 为什么不是初始化?不是在初始化一个临时对象吗?而且,应该是什么? 引用说...当初始化表达式是prvalue ...时,AFAIK这个术语(它甚至是一个术语吗?) “初始化表达式”仅在引用中的 T x = T(T(f())); 等声明的上下文中使用 (timsong-cpp.github.io/cppwp/n4659/dcl.init#17)。 B(B);expression-statement,而不是 declaration

以上是关于为啥在特殊成员函数中将 r 值绑定到 const 左值引用是非法的?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 11 中将非 const 左值引用绑定到右值是不是有效?(修改)

为啥 const 函数可以使用静态成员对象的非常量函数?

Qt 如何在 QtConcurrent 中将指针绑定到非静态成员函数?

C++特殊成员

为啥这个 const auto 变量在 range-for 循环中为类的 const 成员函数编译?

为啥即使对于“写入时复制”的 const 成员函数也返回代理类?