C++传递对象作为函数参数时如何避免拷贝与移动构造
是否可以传对象给函数不触发拷贝?
可以,传引用(左值引用/右值引用)就不会在参数传递阶段触发任何拷贝或移动,引用只是对象的别名,不会创建新的对象实例。你代码中出现的移动构造,和参数传递、栈帧位置没有关系,是后续存入vector的逻辑触发的。
你代码中移动构造的触发原因
先梳理L.Add(Entity(10, 20))的完整执行流程:
- 首先执行
Entity(10,20),在main函数的栈空间构造临时Entity对象,这一步触发你写的构造函数,打印DEFAULT。 - 这个临时对象是右值,可以直接绑定到
Add函数的右值引用参数Entity&& en上——这一步只是给已经存在的临时对象起了个别名,没有新对象创建,不会触发拷贝/移动。 - 执行函数内的
vec.emplace_back(std::move(en)),这一步才是移动构造的触发点:- 这里有个必须记住的C++规则:有名字的右值引用本身是左值。
en是个有变量名的参数,哪怕它类型是右值引用,直接传入emplace_back也会被当作左值处理,触发拷贝构造。 - 你加了
std::move(en),相当于把en强制转换为可被移动的亡值,告诉编译器可以拿走这个对象的资源,emplace_back才会调用Entity的移动构造,在vector自己管理的堆内存里创建一个新的Entity实例,把原临时对象的内容移过去。
- 这里有个必须记住的C++规则:有名字的右值引用本身是左值。
这里要明确一个基本事实:只要你传入
Add的是一个已经构造完成的Entity对象,不管你传值、传左值引用还是右值引用,这个对象的内存都不在vector的存储空间内。vector要持有这个对象,必须在自己的内存区域构造一个属于自己的Entity实例,要么从原对象拷贝,要么从原对象移动——对象一旦构造完成,内存地址就不能改变,你不可能直接把栈上的对象“挪”到vector的堆内存里而不执行构造函数。你写的Add(int, int)之所以没有拷贝/移动,本质是你根本没在外面提前构造Entity,直接把构造参数传给emplace_back,让vector在自己的内存里直接把对象构造出来,自然不需要额外的拷/挪。
如何避免不必要的移动?
分两种场景处理:
- 如果你确实需要先单独构造好Entity对象,再传给
Add存入vector,那这个移动(或者拷贝)是无法完全消除的。对你写的这种只有两个int成员的Entity来说,移动构造和拷贝构造的开销完全一致,没有额外成本,不需要刻意规避。 - 如果你想要达到和
Add(int, int)一样的零额外拷贝/移动效果,直接写一个完美转发的可变参数模板重载即可,不需要单独写接收Entity的重载:
#include <utility> // for std::forward class List { public: List(){vec.reserve(25);} // 任意参数都可以直接透传给emplace_back,自动选择最优构造方式 template<typename... Args> void Add(Args&&... args) { vec.emplace_back(std::forward<Args>(args)...); } private: std::vector<Entity> vec; };
这个写法会自动适配所有传参场景:
- 传
100,200这类构造参数时,直接在vector内存就地构造Entity,和你之前的Add(int,int)行为完全一致,无拷贝无移动; - 传
Entity(10,20)这类右值Entity时,自动触发移动构造; - 传已经存在的左值Entity时,自动触发拷贝构造,不需要你额外写左值引用的重载。
内容的提问来源于stack exchange,提问作者Moe
相关产品推荐
相关产品推荐

