为何使用std::make_pair返回pair时结构化绑定无法触发RVO?
std::pair返回场景下拷贝构造触发的原因说明
你对RVO(返回值优化)的适用边界存在认知偏差,C++标准规定的强制拷贝消除仅覆盖极特定的场景,结构化绑定配合std::pair返回值的场景并不完全在强制消除范围内。
强制拷贝消除的标准生效场景
从C++17版本开始,编译器必须执行拷贝/移动构造消除的场景只有两类:
return语句的操作数是与函数返回值类型完全一致的纯右值(prvalue)- 变量初始化时,初始化表达式是与变量类型完全一致的纯右值
你写的第二个测试用例完全符合第一类规则:func返回类型为Test,return的操作数是Test()这个同类型纯右值,因此拷贝构造被强制消除,全程不会触发拷贝调用。
pair场景下两次拷贝的真实来源
你观察到的两次拷贝构造调用,既不是返回值传递过程产生的,也不是结构化绑定产生的,根源来自std::make_pair的实现逻辑和你的Test类型的定义:
std::make_pair的核心逻辑是接收传入参数,将其构造为std::pair内部存储的两个成员对象。你传入的两个Test()是临时纯右值,但由于你自定义了拷贝构造函数,编译器不会为Test自动生成默认移动构造函数,因此make_pair在构造pair内部成员时,无法调用移动构造,只能退而求其次调用拷贝构造,这就是两次输出的来源。- 实际上
make_pair返回的std::pair<Test, Test>纯右值本身是会被RVO优化的,会直接构造在main函数的栈帧内存中;后续的结构化绑定也只是直接把名字a、b绑定到这个pair的两个内部成员上,不会额外产生拷贝操作。
验证与优化方案
你可以通过两个简单测试验证上述结论:
- 为
Test添加默认移动构造声明Test(Test&&) = default;,重新编译运行就会发现拷贝构造不再被调用(移动构造的调用通常也会被编译器进一步优化消除) - 如果要完全避免构造过程中的移动/拷贝,可以使用分段构造方式直接在pair的内存位置就地构造对象,修改func实现如下:
#include <iostream> #include <utility> #include <tuple> using namespace std; struct Test { Test() {} Test(const Test &other) {cout << "Test copy constructor called\n";} }; auto func() { // 直接在pair的成员存储位置构造两个Test对象,无拷贝/移动 return pair<Test, Test>(piecewise_construct, tuple<>(), tuple<>()); } int main() { auto [a, b] = func(); return 0; }
编译运行上述代码,不会有任何拷贝构造的输出。
内容的提问来源于stack exchange,提问作者Christopher Miller
相关产品推荐
相关产品推荐

