当前位置: 首页> 文旅> 艺术 > C++ copy elision

C++ copy elision

时间:2025/7/10 7:13:38来源:https://blog.csdn.net/lushanshan07170717/article/details/138001956 浏览次数:2次

C++ copy elision

环境

gcc version 9.4.0
c++14

示例代码

//test.cpp
#include <iostream>
class Test {public:Test() {std::cout << "Test Constructor..." << std::endl;}Test(const Test &other) {std::cout << "Test Copy Constructor..." << std::endl;}
};Test getTest() {Test t;return std::move(t);
}
int main() {Test t = getTest();return 0;
}

编译错误

编译选项为 -Werror

  • 编译指令
g++ test.cpp -o test -Werror
  • 报错如下:
test.cpp:766:19: error: moving a local object in a return statement prevents copy elision [-Werror=pessimizing-move]766 |   return std::move(t);|          ~~~~~~~~~^~~
test.cpp:766:19: note: remove ‘std::move’ call
cc1plus: all warnings being treated as errors

不指定 -Werror 编译选项

  • 编译指令
g++ test.cpp -o test
  • 报错如下:
xx.cpp:761:19: warning: moving a local object in a return statement prevents copy elision [-Wpessimizing-move]761 |   return std::move(t);|          ~~~~~~~~~^~~
xx.cpp:761:19: note: remove ‘std::move’ call

报错解释

错误(警告):返回语句中使用std::move移动一个本地对象,会阻止copy elision优化。
提示:移除 ‘std::move’ 调用

解决办法

就如编译器提示的,在函数中返回一个本地对象时,直接返回即可,不要加上std::move。

深入理解copy elision

RVO 和NRVO

在C++17之前,标准并没有规定copy elision,编译器帮我们实现copy elision
在C++17及之后,标准从编译器借取经验,实现了copy elision。并规定RVO(return value optimization)必须要用copy elision,而NRVO(named return value optimization)则可以不用(由编译器决定)。
其中RVO指返回无名字的对象,NRVO正好相反表示返回有名字的对象。

Test getTest() { // RVOreturn Test();
}
Test getTest() { // NRVOTest t;return t;
}

手动禁用编译器的copy elision

编译时,通过编译选项-fno-elide-constructors告诉编译器不要帮我们 copy elision。

测试:C++14中copy elision

NROV

  • 代码
Test getTest() { // NRVOTest t;return t;
}
编译器参与copy elision
  • 编译
g++ test.cpp -o test -std=c++14
  • 运行
Test Constructor...
  • 现象
    按照正常的程序执行流程,getTest函数在返回时执行一次拷贝构造函数,在main函数中将getTest的返回值赋值给 t 时再执行一次拷贝构造函数。
    期待的运行结果应该是调用一次构造函数,两次拷贝构造函数,即:
Test Constructor...
Test Copy Constructor...
Test Copy Constructor...

但通过上面测试我们发现,程序只调用了一次构造函数,并没有调用拷贝构造函数。这是由于编译器默认已经进行了copy elision(就是不copy了,直接在函数调用的地方执行构造)。
这是一种非常高效的编译器优化,帮助我们省了两次拷贝构造操作。

编译器不参与copy elision
  • 编译
g++ test.cpp -o test -std=c++14 -fno-elide-constructors
  • 运行
Test Constructor...
Test Copy Constructor...
Test Copy Constructor...
  • 现象
    正如前面期待的,程序进行了两次拷贝构造函数的调用。

ROV

  • 代码
Test getTest() { // RVOreturn Test();
}
编译器参与copy elision
  • 编译
g++ test.cpp -o test -std=c++14
  • 运行
Test Constructor...
编译器不参与copy elision
  • 编译
g++ test.cpp -o test -std=c++14 -fno-elide-constructors
  • 运行
Test Constructor...
Test Copy Constructor...
Test Copy Constructor...

小结

在C++14中,无论是RVO还是NRVO,copy elision都是由编译器实现的。

测试:C++17中copy elision

NRVO

  • 代码
Test getTest() { // NRVOTest t;return t;
}
编译器参与copy elision
  • 编译
g++ test.cpp -o test -std=c++17
  • 运行
Test Constructor...
编译器不参与copy elision
g++ test.cpp -o test -std=c++17 -fno-elide-constructors
  • 运行
Test Constructor...
Test Copy Constructor...

RVO

  • 代码
Test getTest() { // RVOreturn Test();
}
编译器参与copy elision
  • 编译
g++ test.cpp -o test -std=c++17
  • 运行
Test Constructor...
编译器不参与copy elision
g++ test.cpp -o test -std=c++17 -fno-elide-constructors
  • 运行
Test Constructor...

小结

在C++17中,标准实现了copy elision,编译器终于可以退居二线。
在返回RVO临时对象时,必须采用copy elision,不能调用其拷贝构造函数,前面测试可以看出无论是否禁用编译器的copy elistion功能,程序都不会调用拷贝构造函数。
但在返回NRVO临时对象时,C++好像有些偷懒,不像编译器会把所有的拷贝构造函数copy elision掉,只copy elision掉了一次。

那么NRVO情况下,究竟copy elision掉的是哪次的拷贝构造函数?下面通过测试来一探究竟:

NRVO copy elision探究

我们去掉最后一次拷贝构造,即在函数返回临时对象时,我们不接收。

  • 代码
Test getTest() {Test t;return std::move(t);
}
int main() {getTest();return 0;
}
  • C++17编译
g++ test.cpp -o test -std=c++17 -fno-elide-constructors
  • 运行
Test Constructor...
Test Copy Constructor...
  • 现象
    我们发现运行结果与代码修改前一致。
  • 结论
    C++17 copy elision掉的就是最后一次拷贝构造函数,即将函数返回的临时对象拷贝到主函数临时对象的这一次。
    因此在,NRVO情况下,在将临时对象进行返回前还是会进行一次复制,只在将复制后的对象用来构造主函数的临时对象时才采用copy elision。

既然编译器会帮我们copy elision,那么是不是就可以删除掉拷贝构造函数?

测试NRVO,删除掉拷贝构造函数

  • 代码
//test.cpp
#include <iostream>
class Test {public:Test() {std::cout << "Test Constructor..." << std::endl;}Test(const Test &other) = delete;
};Test getTest() {Test t;return t;
}
int main() {Test t = getTest();return 0;
}
  • 编译运行 C++17
david@DESKTOP-J901GTO:~$ g++ test.cpp  -o test -std=c++17
test.cpp: In function ‘Test getTest()’:
test.cpp:21:10: error: use of deleted function ‘Test::Test(const Test&)21 |   return t;|          ^
test.cpp:10:5: note: declared here10 |     Test(const Test &other) = delete;|     ^~~~
  • 编译运行 C++14
david@DESKTOP-J901GTO:~$ g++ test.cpp  -o test -std=c++14
test.cpp: In function ‘Test getTest()’:
test.cpp:20:18: error: use of deleted function ‘Test::Test(const Test&)20 |   Test t; return t;|                  ^
test.cpp:10:5: note: declared here10 |     Test(const Test &other) = delete;|     ^~~~
  • 现象
    编译报错,这是NRVO情况下,那么在RVO情况下呢

测试RVO,删除掉拷贝构造函数

将上面代码改为RVO

  • 代码
Test getTest() {return Test();
}
  • 编译运行 C++17
david@DESKTOP-J901GTO:~$ g++ test.cpp  -o test -std=c++17
david@DESKTOP-J901GTO:~$ ./test
Test Constructor...
  • 编译运行 C++14
david@DESKTOP-J901GTO:~$ g++ test.cpp  -o test -std=c++14
test.cpp: In function ‘Test getTest()’:
test.cpp:21:15: error: use of deleted function ‘Test::Test(const Test&)21 |   return Test();|               ^
test.cpp:10:5: note: declared here10 |     Test(const Test &other) = delete;|     ^~~~

结论

针对RVO情况,如果在C++17及以上,可以删除拷贝构造函数。
其它所有情况,不能删除拷贝构造函数。

关键字:C++ copy elision

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: