C++如何返回const引用传入的重量级对象且避免拷贝?
问题解答
问题根源
- 当前代码触发拷贝构造的核心原因:
std::move作用在const heavy_obj&类型的形参上时,得到的是const heavy_obj&&类型,而常规移动构造的形参是heavy_obj&&(非const右值引用),无法绑定到带const的右值,因此重载决议会降级选择接收const heavy_obj&的拷贝构造函数。 - 不可以将移动构造改为
heavy_obj(const heavy_obj&& t):移动构造的核心语义是转移原对象的资源,必然需要修改原对象的成员状态,添加const修饰后无法修改原对象,完全违背移动设计的初衷,除了极特殊的语义标记场景,不要使用这种写法。
解决方案
你可以通过完美转发实现同时接收左值、右值入参,且在入参为可修改右值时自动触发移动构造,仅在入参为const左值时才触发拷贝:
方案1:模板+完美转发(推荐,代码复用性高)
#include <iostream> #include <utility> #include <type_traits> class heavy_obj { public: heavy_obj() { std::cout << "Standard" << std::endl; } heavy_obj(const heavy_obj&) { std::cout << "Copy" << std::endl; } heavy_obj(heavy_obj&&) noexcept { std::cout << "Move" << std::endl; } }; template<typename T> std::decay_t<T> return_obj_or_default(T&& t, bool ret) { if(ret) { // 保留入参的原始值类型和const属性,右值则移动,左值则拷贝 return std::forward<T>(t); } else { return heavy_obj(); } } int main() { heavy_obj t; std::cout << "Test1: 传入左值" << std::endl; heavy_obj t2 = return_obj_or_default(t, true); // 入参为左值,调用拷贝构造 std::cout << "Test2: 传入右值" << std::endl; heavy_obj t3 = return_obj_or_default(heavy_obj(), true); // 入参为右值,调用移动构造 return 0; }
输出结果:
Standard Test1: 传入左值 Copy Test2: 传入右值 Standard Move
符合预期:右值入参触发移动,无额外拷贝开销。
方案2:函数重载(无需模板,逻辑更直观)
如果不想使用模板,也可以写两个重载函数分别处理左值和右值场景:
// 处理const左值入参,只能拷贝 heavy_obj return_obj_or_default(const heavy_obj& t, bool ret) { if(ret) return t; return heavy_obj(); } // 处理右值入参,可以移动 heavy_obj return_obj_or_default(heavy_obj&& t, bool ret) { if(ret) return std::move(t); return heavy_obj(); }
效果和模板方案完全一致。
补充说明
如果传入的是const修饰的左值,无论用哪种方案都会触发拷贝,这是符合C++语义的:const对象不允许被修改,自然不能转移它的资源,无法触发移动构造。
内容的提问来源于stack exchange,提问作者Mike van Dyke
相关产品推荐
相关产品推荐

