如何在vector中原位构造类对象以避免拷贝构造?
问题描述
我正在开发一个自定义类,需要创建该类的std::vector容器,希望在容器中原位构造对象而非使用拷贝构造,但目前看来拷贝构造似乎无法避免。以下是使用C++20和MSVC编译器编写的代码:
#include <iostream> #include <string> #include <vector> class MyData { public: int age; std::string name; MyData(int age, std::string name) : age(age), name(name) { std::cout << "MyData::MyData(int, std::string)\n"; } MyData(const MyData& myData) : age(myData.age), name(myData.name) { std::cout << "MyData::MyData(const MyData&)\n"; } MyData(MyData&& myData) : age(std::move(myData.age)), name(std::move(myData.name)) { std::cout << "MyData::MyData(MyData&&)\n"; } ~MyData() { std::cout << "MyData::~MyData()\n"; } }; #define DEBUG(...) std::cout << "Exec: " #__VA_ARGS__ << ";\n"; __VA_ARGS__ int main() { DEBUG(std::vector<MyData> sb1); DEBUG(sb1.emplace_back(MyData{ 32, "SJ" })); DEBUG(sb1.emplace_back(MyData{ 42, "SJ" })); }
代码运行输出如下:
Exec: std::vector<MyData> sb1; Exec: sb1.emplace_back(MyData{ 32, "SJ" }); MyData::MyData(int, std::string) MyData::MyData(MyData&&) MyData::~MyData() Exec: sb1.emplace_back(MyData{ 42, "SJ" }); MyData::MyData(int, std::string) MyData::MyData(MyData&&) MyData::MyData(const MyData&) MyData::~MyData() MyData::~MyData() MyData::~MyData() MyData::~MyData() C:\Users\XOXOX\source\repos\cpp_stack\x64\Debug\cpp_stack.exe (process 12832) exited with code 0. Press any key to close this window . . .
问题分析与解决办法
为什么会出现拷贝/移动构造?
- 错误使用
emplace_back:你当前调用emplace_back(MyData{32, "SJ"})时,先构造了一个临时MyData对象,再通过移动构造把这个临时对象移入容器——这完全违背了emplace_back原位构造的初衷,所以会触发移动构造和临时对象的析构。 - vector扩容触发拷贝:第二次
emplace_back时,vector的初始容量不足(默认初始容量通常为0或1),需要扩容。由于你的移动构造函数没有标记noexcept,为了保证异常安全,vector会选择拷贝构造而非移动构造来转移现有元素,这就是输出中拷贝构造被调用的原因。
解决步骤
- 正确使用
emplace_back:直接传入构造函数的参数,让emplace_back在容器的内存空间里直接构造对象,完全避免临时对象和移动构造:sb1.emplace_back(32, "SJ"); // 直接原位构造,无临时对象 sb1.emplace_back(42, "SJ"); - 给移动构造函数添加
noexcept:标记移动构造不会抛出异常,这样vector扩容时会优先使用移动构造而非拷贝构造:MyData(MyData&& myData) noexcept : age(std::move(myData.age)), name(std::move(myData.name)) { std::cout << "MyData::MyData(MyData&&)\n"; } - 提前预留容器空间:如果知道需要存储的元素数量,调用
reserve提前分配足够内存,避免扩容操作:std::vector<MyData> sb1; sb1.reserve(2); // 预留2个元素的空间,后续emplace_back不会触发扩容
优化后的代码示例
#include <iostream> #include <string> #include <vector> class MyData { public: int age; std::string name; MyData(int age, std::string name) : age(age), name(name) { std::cout << "MyData::MyData(int, std::string)\n"; } MyData(const MyData& myData) : age(myData.age), name(myData.name) { std::cout << "MyData::MyData(const MyData&)\n"; } MyData(MyData&& myData) noexcept : age(std::move(myData.age)), name(std::move(myData.name)) { std::cout << "MyData::MyData(MyData&&)\n"; } ~MyData() { std::cout << "MyData::~MyData()\n"; } }; #define DEBUG(...) std::cout << "Exec: " #__VA_ARGS__ << ";\n"; __VA_ARGS__ int main() { DEBUG(std::vector<MyData> sb1); DEBUG(sb1.reserve(2)); DEBUG(sb1.emplace_back(32, "SJ")); DEBUG(sb1.emplace_back(42, "SJ")); }
优化后的输出
Exec: std::vector<MyData> sb1; Exec: sb1.reserve(2); Exec: sb1.emplace_back(32, "SJ"); MyData::MyData(int, std::string) Exec: sb1.emplace_back(42, "SJ"); MyData::MyData(int, std::string) MyData::~MyData() MyData::~MyData()
可以看到,现在只有两次原位构造,没有任何拷贝或移动构造,完全符合需求。
内容的提问来源于stack exchange,提问作者Milad Rasouli
相关产品推荐
相关产品推荐

