提问者:小点点

返回不可复制常数值的函数的非直观RVO?


考虑以下C中的示例代码

struct A{
    A() = default;
    A(const A&) = delete;
};

const A f(){ return A{}; }

int main(){
    const A& a = f(); // OK
    // A& b = f();    // Error: cannot convert 'const A' to 'A&'
    const A c = f();  // OK: Copy elision
    A d = f();        // OK!?
}

类< code>A是不可复制的,但是由于强制复制省略,我们可以将< code>f()的结果放入变量中。根据cppreference.com的这个页面,上面的行为是完全合法的,因为它指定当复制省略发生时,返回值上的< code>const量词被忽略。

然而,这种行为对我来说似乎非常违反直觉。由于 A 是不可复制的,我觉得不应该有办法将 const A 变成 A(除非你有 A::Aconst A

(我在尝试实现自己的类型擦除类时遇到了这个问题。我在函数f()的返回值上指定const的全部目的是防止用户获得对象的非const左值引用,但这个规范似乎打开了一个洞。)

编辑:这个例子可能会更清楚地显示反直觉:让我们考虑一个可移动但不可复制的A类。

struct A{
    A() = default;
    A(const A&) = delete;
    A(A&&) = default;
};

int main(){
    // C++14: move / C++17: copy elision
    A a = A{}; 

    // C++14: error (deleted copy constructor) / C++17: copy elision(!!)
    A b = static_cast<const A>(A{}); 
}

这不能用 C 语言编译


共2个答案

匿名用户

拒绝使用可移动对象进行此类初始化将是一个重大更改,因为该语言的先前版本将产生移动到那里。让它依赖于变量的 cv 资格是非常微妙的。

对于可复制对象,新行为实际上是旧行为的子集:从const A返回值到A变量的副本可能已被省略,在这种情况下,它们与C 17中的对象相同。

与此同时,自从C 11以来,常量返回值就不太受欢迎,因为在C 11中< code>f(return_const())失去了移入(by-value)参数的能力。

C 17 对 prvalues 的处理(“强制复制省略”是一个仅在历史上有意义的名称)支持其他情况,例如本例中不可移动对象的返回:该函数被认为指定如何初始化它“返回”的对象,而不是实际返回完成的对象。在选择此模型时,通常认为支持更多种类的有效代码比支持现有的习惯用法以防止滥用更重要。

匿名用户

Elision 是将多个对象合并为一个。

struct Bob {};

Bob foo() {
  const Bob b;
  return b;
}

const Bob x = foo();

在这里,对象 bxfoo() 的匿名返回值被省略到同一个对象中。

此对象的常量ness取决于您使用的声明。C标准委员会决定,这一效率的提高超过了出现和消失的const

现在,在这种情况下,保证省略不成立,因为const Bob b不能保证省略。

const Bob bar() {
  return {};
}
Bob foo() {
  return bar();
}

const Bob x = foo();

这里我们保证了省略,所有3个(或4个相关的)对象变成了同一个对象。