C++ 动态数组 vector push_back的拷贝次数如何计算

该代码示例展示了在C++中如何使用`vector`管理`Vertex`结构体对象时进行优化。通过`reserve`预分配空间减少拷贝,使用`emplace_back`直接在容器内构造对象避免额外拷贝,以及使用`erase`删除元素和`clear`清空容器。文章讨论了`push_back`导致的拷贝次数增加问题,并提供了优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include <iostream>
#include <string>
#include <vector>

struct Vertex
{
    float x, y, z;
    
    Vertex(float x, float y, float z)
            : x(x), y(y), z(z)
    {
    }
    
    Vertex(const Vertex& vertex)
            : x(vertex.x), y(vertex.y), z(vertex.z)
    {
        std::cout << "Copied" << std::endl;
    }
};

std::ostream& operator<<(std::ostream& stream, const Vertex& vertex)
{
    stream << vertex.x << ", " << vertex.y << ", " << vertex.z;
    return stream;
}

void Function(const std::vector<Vertex>& vertices)
{
}

int main()
{
    std::vector<Vertex> vertices;
    
    // optimize one 
    vertices.reserve(3);
    
    vertices.push_back(Vertex(1, 2, 3 ));
    vertices.push_back(Vertex(4, 5, 6 ));
    vertices.push_back(Vertex(7, 8, 9 ));
    
    // optimize two
    vertices.emplace_back(Vertex(1, 2, 3 ));
    vertices.emplace_back(Vertex(1, 2, 3 ));
    
    for (int i = 0; i < vertices.size(); i++)
        std::cout << vertices[i] << std::endl;

    // Clear the second
    vertices.erase(vertices.begin() + 1);

    for (Vertex v : vertices)
        std::cout << v << std::endl;

    for( Vertex& v : vertices)
        std::cout << v << std::endl;

    // Clear Vertices
    vertices.clear();

    std::cin.get();
    

}

发生拷贝的地方:

        使用 push_back 时, vector会逐渐变大, 1 --> 2 --> 4 --> 8

        从main函数的堆栈里构建对象,然后拷贝到vector中

        push_back 1 次, 复制 1 次        1

        push_back 2 次, 复制 3 次        2 + 1

        push_back 3 次, 复制 6 次        3 + 3

        push_back 4 次, 复制 7 次        4 + 3

### C++ 中 `std::vector` 的 `emplace_back` 方法详解 #### 什么是 `emplace_back` `emplace_back` 是 C++11 引入的一个成员函数,用于在 `std::vector` 容器的末尾直接构造一个新元素。它通过将参数转发给目标类型的构造函数来实现这一点。 与传统的 `push_back` 不同的是,`emplace_back` 避免了额外的复制或移动操作,因为它是在容器内部直接构造对象[^3]。 --- #### 函数签名 以下是 `emplace_back` 的标准定义: ```cpp template< class... Args > void emplace_back(Args&&... args); ``` 该模板允许传递任意数量和类型的参数到目标类型的构造函数中。这些参数会被完美转发(perfect forwarding),从而减少不必要的中间对象创建。 --- #### 使用场景分析 ##### 场景一:基本数据类型 对于简单的内置类型(如 `int`, `double` 等),`emplace_back` 和 `push_back` 行为几乎一致,因为它们都只是简单地存储值。 ```cpp #include <iostream> #include <vector> int main() { std::vector<int> vec; vec.emplace_back(42); // 构造 int 类型并添加到 vector 尾部 std::cout << vec[0] << "\n"; // 输出 42 } ``` ##### 场景二:复杂自定义类 当处理复杂的自定义类时,`emplace_back` 显示出了它的优势——减少了临时对象的创建次数。 假设有一个类 `Test` 如下所示: ```cpp class Test { public: explicit Test(int value) : data(value) { std::cout << "construct\n"; } ~Test() { std::cout << "destruct\n"; } private: int data; }; ``` 使用 `emplace_back` 添加对象时会直接调用其构造函数: ```cpp #include <vector> #include <iostream> int main() { std::vector<Test> vec; vec.emplace_back(42); // 调用了 Test 的构造函数 // 并未创建任何临时对象 } // 输出: construct // destruct (程序结束时销毁) ``` 而如果改用 `push_back`,则可能会涉及额外的一次拷贝或移动操作: ```cpp vec.push_back(Test(42)); // 创建了一个临时对象 Test(42),随后将其拷贝/移动至 vector 中 // 这里有两次构造过程以及一次析构过程 ``` ##### 场景三:潜在风险 尽管 `emplace_back` 提供了更高的效率,但在某些情况下也可能引入隐患。例如,在向含有特定约束条件的数据结构中插入元素时,错误的参数可能导致意外行为。 考虑下面的例子: ```cpp std::vector<std::regex> v; v.emplace_back(nullptr); // 编译可以通过,但运行时抛出异常[^1] ``` 上述代码试图将一个空指针作为正则表达式的初始化参数传入,这显然不符合预期逻辑,最终会在运行期触发异常。 因此,在实际开发过程中应当谨慎对待每种情况下的输入验证工作。 --- #### 性能对比 (`push_back` vs `emplace_back`) | **特性** | **`push_back`** | **`emplace_back`** | |--------------------------|--------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| | 参数传递方式 | 接收已存在的对象副本 | 直接利用原始参数构建所需实例 | | 是否支持原位构造 | 否 | 是 | | 复杂度(理想状态) | 可能存在额外的拷贝或者转移开销 | 更高效 | 具体表现取决于所管理的对象是否有有效的移动语义及其内部机制设计等因素影响[^4]。 --- #### 示例代码展示 以下是一些具体的例子说明两者的差异: ###### 测试案例 1 - 左值插入 ```cpp struct Data { Data(const char* str): name(str){ std::cout << "Constructing with string.\n";} Data(Data const& other):name(other.name+" copy"){std::cout<<"Copy constructing.\n";} ~Data(){if(!name.empty())std::cout<<name<<" destroyed.\n";} std::string name; }; int main(){ std::vector<Data> container; Data d("Original"); container.emplace_back(d); /* Output: Constructing with string. Copy constructing. */ return 0; } ``` 这里可以看到即使采用 `emplace_back` ,由于提供了左值仍需执行一次深拷贝动作。 ###### 测试案例 2 - 移动语义应用 ```cpp container.emplace_back(std::move(d));/* Only one construction happens here due to move semantics.*/ ``` 此时仅发生单次资源占有权转让而非真正意义上的内容迁移。 --- ### 结论 综上所述,虽然两者都能完成相似的任务即扩展动态数组长度的同时追加指定项进去;但是考虑到性能优化方面的需求的话,则推荐优先选用后者—`emplace_back()` 来达成目的[^2].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值