【问题标题】: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
}

【问题讨论】:

    标签: c++ c++11 c++17 language-lawyer rvalue


    【解决方案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
      

    注意:上述规则未指定优化:prvaluestemporaries 的 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
    猜你喜欢
    • 1970-01-01
    • 2017-04-15
    • 1970-01-01
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多