C++中避免冗余拷贝:T、T&、T const&、T&&的适用场景
C++中T、T&、T const&、T&&的使用场景辨析
测试发现与核心疑问
为理清T、T const&、T&&的使用逻辑,我通过测试代码验证拷贝与移动语义,发现不同构造函数定义下编译器行为存在明显差异:
- 默认合成构造函数时,
test3的临时对象直接就地构造,无拷贝/移动开销; - 添加显式拷贝+移动构造函数后,
test3触发移动操作; - 仅定义值传递构造函数时,所有测试案例均出现额外拷贝,
test3的临时对象未按预期触发移动。
此前我认为值传递适合需拷贝资源的场景,临时对象可触发移动或就地构造,但测试结果与此认知冲突。现需明确:为避免昂贵拷贝并支持就地构造,函数(含构造函数)中应何时使用T、T&、T const&、T&&(及T const&&)?
测试代码
#include <iostream> #include <utility> #include <vector> template< int i > struct Example { Example(){ std::cout << "("<< i<<") was made\n"; } Example( Example const & ){ std::cout << "("<< i<<") was copied\n"; } Example( Example && ){ std::cout << "("<< i<<") was moved\n"; } ~Example(){ std::cout << "("<< i<<") was destroyed\n"; } std::vector< int > x = {1,2,3,4}; }; template< int i > struct Test { Example< i > example; // 仅打开下方值传递构造函数时会出现额外拷贝 // Test( Example< i > example ): example( example ) {} // 显式拷贝+移动构造函数 // Test( Example< i > const & example ): example( example ) {} // Test( Example< i > && example ): example( std::move(example) ) {} }; int main() { Example<1> example1; Example<2> example2; std::cout << "Test1\n"; Test test1{ example1 }; std::cout << "Test2\n"; Test test2{ std::move(example2) }; std::cout << "Test3\n"; Test test3{ Example<3>() }; std::cout << "Tests done \n"; }
初始输出(默认合成构造函数)
(1) was made (2) was made Test1 (1) was copied Test2 (2) was moved Test3 (3) was made Tests done (3) was destroyed (2) was destroyed (1) was destroyed (2) was destroyed (1) was destroyed
各参数类型的适用场景
1. T(值传递)
- 适用场景:明确需要在函数内部创建参数独立副本时使用,比如要修改参数但不影响外部对象,或函数需持有专属副本。
- 注意事项:值传递会先生成参数的临时副本(左值传参是拷贝,右值/临时对象传参是移动),若用该副本初始化成员变量,可能出现两次拷贝(如测试中仅定义值传递构造的场景:先拷贝实参到形参,再拷贝形参到成员)——编译器优化不总能消除这一步开销。
2. T&(非const左值引用)
- 适用场景:需要修改传入的外部对象,或想避免拷贝但仅接受可修改的左值时使用。
- 注意事项:无法绑定临时对象(右值),只能绑定有名字的左值。
3. T const&(const左值引用)
- 适用场景:仅需读取参数值,且希望同时支持左值和右值传参、避免拷贝时使用。
- 注意事项:可绑定左值和临时对象,但无法修改参数;用它初始化成员变量时会触发拷贝构造(const引用无法移动)。
4. T&&(右值引用)
- 适用场景:需要接管传入对象的资源(移动语义),仅接受右值(临时对象、
std::move后的左值)时使用。 - 注意事项:可将参数资源移动到成员变量,避免拷贝;常用于构造函数、赋值运算符及完美转发场景。
5. T const&&(const右值引用)
- 适用场景:极为罕见,仅用于需要接受const临时对象且避免拷贝的特殊场景,日常开发几乎无需考虑。
优化建议(结合测试场景)
要同时实现避免昂贵拷贝+支持就地构造,推荐以下方案:
- 优先使用默认合成构造函数:编译器自动生成的移动/拷贝构造,会对临时对象优先触发就地构造(省略拷贝/移动),这是最简洁高效的方式。
- 显式定义拷贝+移动构造:若需自定义构造逻辑,必须同时提供
T const&和T&&版本,确保左值传参用拷贝、右值/临时对象传参用移动,避免不必要开销。 - 避免单独使用值传递构造:单独的值传递构造会引入额外拷贝步骤,除非确实需要先创建副本再处理。
完美转发补充
若要编写通用构造函数支持任意参数类型,可使用**模板+std::forward**实现完美转发:
template<typename T> Test(T&& example) : example(std::forward<T>(example)) {}
该方式会自动转发参数的左值/右值属性,自动选择拷贝或移动,甚至支持就地构造。
内容的提问来源于stack exchange,提问作者Sam Coutteau
相关产品推荐
相关产品推荐

