如何同时兼顾POD结构体的初始化列表使用与原地构造以避免拷贝?
如何同时兼顾POD结构体的初始化列表使用与原地构造以避免拷贝?
哈哈,先帮你厘清一个常见误区:你提到的vec.push_back(A{ .a = 4, .b = 2 });其实在C++11及以后,并不会触发拷贝构造,而是会调用push_back的右值引用版本,进行移动构造。不过对于POD类型来说,移动和拷贝的开销几乎完全一样(都是逐字节复制),而且在开启优化的情况下,编译器大概率会直接把临时对象优化掉(RVO返回值优化),直接在vector的存储空间里构造对象,连移动都省了。不过如果你追求绝对的原地构造,不想有任何临时对象的开销,那确实有更好的办法。
接下来针对你的需求——保留POD的聚合初始化语法(比如.a=4这种指定初始化器),同时实现真正的原地构造——分版本给你解决方案:
C++20及以上:完美兼顾两者
C++20专门修复了聚合类型在emplace_back里的构造问题,现在你可以直接用指定初始化器语法调用emplace_back,完全原地构造,不需要添加任何构造函数,也不会产生临时对象:
std::vector<A> vec; vec.emplace_back(.a = 4, .b = 2); // 直接在vector内存里就地构造A对象
这样既保留了A{.a=3, .b=5}这种聚合初始化的用法,又实现了无拷贝/移动的原地构造,完美符合你的需求。
C++17及更早版本:折中方案
如果你的项目还不能升级到C++20,有两个实用的折中选项:
- 选项1:接受极小开销(甚至被优化掉)
如开头所说,vec.push_back(A{ .a = 4, .b = 2 });在开启-O2及以上优化时,编译器会自动省略临时对象的创建,直接在vector里构造。对于POD这种简单类型,哪怕没优化,移动的开销也可以忽略不计,大部分场景下这已经足够用了。 - 选项2:手动原地构造(牺牲指定初始化器顺序)
如果你非要彻底避免任何临时对象,可以用std::construct_at直接操作vector的内存空间,按结构体成员的顺序传递参数来进行聚合初始化:
这个方法完全没有拷贝/移动,但缺点是必须严格按照结构体成员的顺序传参,不能用std::vector<A> vec; vec.reserve(vec.size() + 1); // 先预留空间,避免扩容 std::construct_at(vec.data() + vec.size(), 4, 2); // 原地构造A,a=4,b=2 vec.resize(vec.size() + 1); // 更新vector的size.a=4这种指定初始化器语法了。
最后再补充一句:你之前担心emplace_back会拷贝其实是误解——如果给emplace_back传的是A{.a=4, .b=2}这种临时对象,它确实会移动构造元素,但如果传的是构造所需的参数(比如C20的指定初始化器,或者C17的成员顺序参数),它就会直接原地构造,不会有拷贝/移动。
内容来源于stack exchange
相关产品推荐
相关产品推荐

