You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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 . . .
问题分析与解决办法

为什么会出现拷贝/移动构造?

  1. 错误使用emplace_back:你当前调用emplace_back(MyData{32, "SJ"})时,先构造了一个临时MyData对象,再通过移动构造把这个临时对象移入容器——这完全违背了emplace_back原位构造的初衷,所以会触发移动构造和临时对象的析构。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 17:18:08