c++中函数返回时的RVO机制和std::move的理解

常见问题

RVO和std::move都能减少对象拷贝时的开销,那他们到底是什么与什么的关系叫?
talk is cheap, show me the code!
以下测试结果均在gcc 4.8.4下进行。
如下代码所示:test_1.cpp

场景一【函数返回非RVO】

class Obj {
public:
    Obj() {
        printf("construct \n");
    }   

    ~Obj() {
        printf("destructor\n");
    }   
    
    Obj(const Obj& o) {
        printf("copy constructor\n");
    }    
};

Obj ReturnObj() {
    Obj obj;
    return obj;
}

int main() 
{
    Obj obj = ReturnObj();
    return 0;
}

举个栗子,一个函数,我们返回一个栈变量的时候,我们知道,正常情况下,是会先把函数中的变量,拷贝到一个临时变量中,再把这个临时变量拷贝到obj这个变量中。
可以使用如下命令进行编绎(把RVO选项关闭):
g++ -std=c++11 -fno-elide-constructors -g -c test.cpp
g++ -std=c++11 -g -fno-elide-constructors -o test test.o

c++中函数返回时的RVO机制和std::move的理解_第1张图片
如我们之前分析的一致,存在两次的拷贝构造

场景二 【转移构造】

我们知道,转移构造,能够减少对象拷贝带来的开销。如果我们把返回值使用std::move转换后再返回呢,是不是可以通过把拷贝构造变成转移构造来降低开销。
代码如下所示:
test_2.cpp


class Obj {
public:
    Obj() {
        printf("construct \n");
    }   

    ~Obj() {
        printf("destructor\n");
    }   
    
    Obj(const Obj& o) {
        printf("copy constructor\n");
    }   

    Obj(Obj&& o) {
        printf("move constructor\n");
    }   
};


Obj ReturnObj() {
    Obj obj;
    return std::move(obj);
}


int main() 
{
    Obj obj = ReturnObj();
    return 0;
    
}

g++ -std=c++11 -fno-elide-constructors -g -c test.cpp
g++ -std=c++11 -g -fno-elide-constructors -o test test.o

执行结果如下:
在这里插入图片描述
注:

  1. 第一次转移构造,是把栈变量obj转移到临时变量中。第二次转移构造,则是把临时变量转移到main函数中的obj变量。
  2. 这个场景对返回值是否使用std::move,其实不影响执行结果。因为返回的结果是先Obj tmp = ReturnObj(); 再通过把Obj obj = tmp; 由于tmp是临时变量,我们能够比较好的理解第二次是会调用转移构造。但第一次为什么也是一个转移构造,可以参考之前的一篇文章分析函数返回值的优化技术(RVO和右值引用)

场景三【RVO】

同样是test_1.cpp的代码,我们如果把编绎选项开启RVO,即是默认情况下即会启用。编绎命令:
g++ -std=c++11 -g -c test.cpp
g++ -std=c++11 -g -o test test.o

执行命令后,结果如下:在这里插入图片描述
通过执行结果可以看到,RVO极大提升了执行效率。从三次构造对象变成了一次。
RVO的执行过程,是直接用栈里面的对象去初始化函数外的对象。可以类似于使用
Obj obj;
ReturnObj(&obj);

场景四 【RVO and std::move】

对于test_2.cpp代码,我们使用RVO选项进行编绎。
命令如下:
g++ -std=c++11 -g -c test.cpp
g++ -std=c++11 -g -o test test.o

我们执行结果如下:
在这里插入图片描述
嘿,只剩下一次转移构造了。肿么回事?
因为这里RVO起了作用。对外部的对象,直接使用函数内的临时变量进行转移构造,中间的临时变量被RVO优化掉了。
所以,这里RVO与std::move是并存的,不存在什么排斥关系。

场景四

结论

  1. 函数返回时,该使用临时变量就使用临时变量,RVO效率比使用转移构造要高。转移构造也是一样需要对象创建。
  2. RVO与转移构造,是并存关系。该进行RVO的时候,会进行RVO进行优化,对函数调用也是存在优化作用。

你可能感兴趣的:(C/C++,linux)