c++ - 如何移动一个 const 返回的对象?

标签 c++ c++11 constants rvalue-reference rvo

最近,我一直在阅读this postthat post建议停止返回 const 对象。 此建议也由 Stephan T. Lavavej 在 his talk 中给出在 2013 年的 Going Native 中。

我写了一个非常简单的测试来帮助我理解在所有这些情况下调用了哪个构造函数/运算符:

  • 返回 const 或非 const 对象
  • 如果启动返回值优化 (RVO) 会怎样?
  • 如果启用命名返回值优化 (NRVO) 会怎样?

这是测试:

#include <iostream>

void println(const std::string&s){
    try{std::cout<<s<<std::endl;}
    catch(...){}}

class A{
public:
    int m;
    A():m(0){println("    Default Constructor");}
    A(const A&a):m(a.m){println("    Copy Constructor");}
    A(A&&a):m(a.m){println("    Move Constructor");}
    const A&operator=(const A&a){m=a.m;println("    Copy Operator");return*this;}
    const A&operator=(A&&a){m=a.m;println("    Move Operator");return*this;}
    ~A(){println("    Destructor");}
};

A nrvo(){
    A nrvo;
    nrvo.m=17;
    return nrvo;}

const A cnrvo(){
    A nrvo;
    nrvo.m=17;
    return nrvo;}

A rvo(){
    return A();}

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

A sum(const A&l,const A&r){
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    A sum;
    sum.m=l.m+r.m;
    return sum;}

const A csum(const A&l,const A&r){
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    A sum;
    sum.m=l.m+r.m;
    return sum;}

int main(){
    println("build a");A a;a.m=12;
    println("build b");A b;b.m=5;
    println("Constructor nrvo");A anrvo=nrvo();
    println("Constructor cnrvo");A acnrvo=cnrvo();
    println("Constructor rvo");A arvo=rvo();
    println("Constructor crvo");A acrvo=crvo();
    println("Constructor sum");A asum=sum(a,b);
    println("Constructor csum");A acsum=csum(a,b);
    println("Affectation nrvo");a=nrvo();
    println("Affectation cnrvo");a=cnrvo();
    println("Affectation rvo");a=rvo();
    println("Affectation crvo");a=crvo();
    println("Affectation sum");a=sum(a,b);
    println("Affectation csum");a=csum(a,b);
    println("Done");
    return 0;
}

这是 Release模式下的输出(使用 NRVO 和 RVO):

build a
    Default Constructor
build b
    Default Constructor
Constructor nrvo
    Default Constructor
Constructor cnrvo
    Default Constructor
Constructor rvo
    Default Constructor
Constructor crvo
    Default Constructor
Constructor sum
    Default Constructor
    Move Constructor
    Destructor
Constructor csum
    Default Constructor
    Move Constructor
    Destructor
Affectation nrvo
    Default Constructor
    Move Operator
    Destructor
Affectation cnrvo
    Default Constructor
    Copy Operator
    Destructor
Affectation rvo
    Default Constructor
    Move Operator
    Destructor
Affectation crvo
    Default Constructor
    Copy Operator
    Destructor
Affectation sum
    Copy Constructor
    Move Operator
    Destructor
Affectation csum
    Default Constructor
    Move Constructor
    Destructor
    Copy Operator
    Destructor
Done
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor

我不明白的是: 为什么在“Constructor csum”测试中使用移动构造函数?

返回对象是 const 所以我真的觉得它应该调用复制构造函数。

我在这里错过了什么?

这应该不是编译器的错误,Visual Studio 和 clang 都给出相同的输出。

最佳答案

What I don't understand is this: why is the move constructor used in the "Constructor csum" test ?

在这种特殊情况下,允许编译器执行 [N]RVO,但它没有执行。第二好的事情是移动构造返回的对象。

The return object is const so I really feel like it should call the copy constructor.

这根本不重要。但我想这并不完全显而易见,所以让我们来看看返回值在概念上意味着什么,以及 [N]RVO 是什么。为此,最简单的方法是忽略返回的对象:

T f() {
   T obj;
   return obj;   // [1] Alternatively: return T();
}
void g() {
   f();          // ignore the value
}

在标记为 [1] 的行中,存在从本地/临时对象到返回值的拷贝。 即使该值被完全忽略。这就是您在上面的代码中练习的内容。

如果不忽略返回值,如:

T t = f();

从概念上讲,从返回值到 t 局部变量存在第二个拷贝。在您的所有案例中都删除了第二个拷贝。

对于第一个拷贝,返回的对象是否为const并不重要,编译器根据[conceptual copy/move]构造函数的参数来决定做什么,而不是是否正在构造的对象将是 const 或不是。这与:

// a is convertible to T somehow
const T ct(a);
T t(a);

目标对象是否为 const 并不重要,编译器需要根据参数而不是目标找到最佳构造函数。

现在,如果我们把它带回到你的练习中,为了确保没有调用复制构造函数,你需要修改 return 语句的参数:

A force_copy(const A&l,const A&r){ // A need not be `const`
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    const A sum;
    return sum;
}

这应该会触发复制构造,但是再一次,它足够简单,如果编译器认为合适,它可能会完全删除该拷贝。

关于c++ - 如何移动一个 const 返回的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20545014/

相关文章:

c++ - 从常规 C++ 代码调用 CUDA 代码——整理出 extern "C"

c++ - 在 C++ 中生成组合

c++ - 使用 libsox 将音频转换为 FLAC

c++ - 为什么这个 static_cast 是不允许的?

c - 在函数内使用 const 和 static const 的速度有区别吗?

c++ - QNetworkAccessManager 问题获取网页

c++ - C++11 中的递归 lambda 函数

c++ - 如何在写入其他 std::atomic 之后强制读取 std::atomic?

javascript - 如何使用 const 关键字将 Javascript 常量创建为对象的属性?

PHP错误无法在写入上下文中使用方法返回>值