c++11标准 由于(RVO)Return Value Optimization优化导致无法理解何时调用拷贝构造函数等

c++11标准 由于(RVO)Return Value Optimization优化导致无法理解何时调用拷贝构造函数等,第1张

首先输入测试代码如下
#include
#include
using namespace std;
class Test{
private:
    int i;
    string s;
public:
    Test():i(0),s(""){cout<<toString()<<"执行默认构造函数!"<<endl;}        
    Test(int i,string s):i(i),s(s){cout<<toString()<<"执行带参构造函数!"<<endl;}   
    Test(const Test &test):i(test.i),s(test.s){cout<<toString()<<"执行拷贝构造函数!"<<endl;} 
    ~Test(){cout<<toString()<<"执行析构函数!"<<endl;}
    Test operator+(const Test &test){
        return Test(this->i+test.i,this->s+test.s);
    }
    string toString(){return "str:"+s+" "+"i="+to_string(i)+"  ";}
};

Test getTest(){
    Test test(3,"333");
    cout<<"getTest()-->address:"<<&test<<endl;
    return test;
}
int main(){
    Test t1;
    Test t2(2,"222");
    Test t3 = t2;
    Test t4 = getTest();
    Test t5 = t3+t4;
    cout<<"t4--->address:"<<&t4<<endl;
    cout<<"t5--->address:"<<&t5<<endl;
}
  • 执行命令:g++ -o test.o test.cpp; ./test.o后输出以下内容。
    str: i=0  执行默认构造函数!      // 第25行代码 Test t1;
    str:222 i=2  执行带参构造函数!   // 第26行代码 Test t2(2,"222")
    str:222 i=2  执行拷贝构造函数!   // 第27行代码 Test t3=t2
    str:333 i=3  执行带参构造函数!   // 第20行代码 Test test(3,"333")
    getTest()-->address:0x7ffd832adf20  // 打印函数getTest()中test的地址
    str:222333 i=5  执行带参构造函数!  // 第14行代码 return *****
    t4--->address:0x7ffd832adf20   // 第30行
    t5--->address:0x7ffd832adf50   // 第31行
    str:222333 i=5  执行析构函数!  // t5
    str:333 i=3  执行析构函数!    // t4
    str:222 i=2  执行析构函数!    // t3
    str:222 i=2  执行析构函数!    // t2
    str: i=0  执行析构函数!    // t1
    

思考:在调用getTest()函数的时候,根据所学的c++11标准,返回值应当首先拷贝到一个临时test变量中,然后将该临时的变量再拷贝给t4,所以应当在结果中的第5行之后调用拷贝构造函数给临时变量,再将该临时变量拷贝给t4。
同理Test t5=t3+t4也应当首先将operator+函数内的变量首先通过拷贝构造函数给一个临时变量,再将该临时变量拷贝给t5。
但是再结果中这些步骤均没有体现,并且通过getTest()返回的变量t4的地址与函数内地址相同,这似乎与返回一个引用无异。
通过搜索相关知识,了解到是由于ROV优化的结果。

  • 执行指令:g++ -o test.o test.cpp -fno-elide-constructors; ./test.o后输出以下内容。其中新加入参数的作用是防止ROV优化。
    str: i=0  执行默认构造函数!
    str:222 i=2  执行带参构造函数!
    str:222 i=2  执行拷贝构造函数!
    str:333 i=3  执行带参构造函数!
    getTest()-->address:0x7fff8b7ebf50
    str:333 i=3  执行拷贝构造函数!  // 可以推断出执行的位置是第22行  return test;
    str:333 i=3  执行析构函数!      // 位置是第22行  return test;
    str:222333 i=5  执行带参构造函数!
    t4--->address:0x7fff8b7ec040
    t5--->address:0x7fff8b7ec070
    str:222333 i=5  执行析构函数!
    str:333 i=3  执行析构函数!
    str:222 i=2  执行析构函数!
    str:222 i=2  执行析构函数!
    str: i=0  执行析构函数!
    

思考:跟第一次不同的是增加了第5,6行的输出,在函数返回test时将test拷贝给一个临时变量,并将函数内的临时变量析构掉。可以看到t4与函数getTest内的test变量地址不一样了。
执行第28行时直接将该临时变量命名给t4而不是拷贝给t4
同样在执行operator+直接将未命名的临时变量赋值给t5,没有拷贝给临时变量的过程。与所学的仍然有出入。

  • 通过命令 info g++并搜索default发现默认的标准是c++17。因此执行以下指令:g++ -o test.o test.cpp -fno-elide-constructors -std=c++11; ./test.o 。新加参数指明使用c++11标准。输出如下:
    str: i=0  执行默认构造函数!
    str:222 i=2  执行带参构造函数!
    str:222 i=2  执行拷贝构造函数!
    str:333 i=3  执行带参构造函数!
    getTest()-->address:0x7ffc4d62e0b0
    str:333 i=3  执行拷贝构造函数!   // 第22行 return test 将test拷贝给一个临时变量
    str:333 i=3  执行析构函数!       // 第22行 析构掉test
    str:333 i=3  执行拷贝构造函数!   // 第28行 将临时变量拷贝给t4,
    str:333 i=3  执行析构函数!       // 第28行,析构掉临时变量。
    str:222333 i=5  执行带参构造函数!// 第14行
    str:222333 i=5  执行拷贝构造函数!// 第14行,拷贝给一个临时变量
    str:222333 i=5  执行析构函数!    // 第14行,析构掉带参的构造函数构造的变量
    str:222333 i=5  执行拷贝构造函数!// 第29行,将临时变量拷贝给t5
    str:222333 i=5  执行析构函数!    // 第29行,析构掉临时变量。
    t4--->address:0x7ffc4d62e1a0
    t5--->address:0x7ffc4d62e1d0
    str:222333 i=5  执行析构函数!
    str:333 i=3  执行析构函数!
    str:222 i=2  执行析构函数!
    str:222 i=2  执行析构函数!
    str: i=0  执行析构函数!
    

思考:这次就完全按照c++11所教的流程执行了下来。
但是可以看到经过ROV优化后的步骤要少很多,避免了很多次构造函数和拷贝构造函数的调用。

参考:https://zhuanlan.zhihu.com/p/56008627

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/707195.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-24
下一篇 2022-04-24

发表评论

登录后才能评论

评论列表(0条)

保存