【C++】vector中emplace_back的用法

emplace_back 的工作机制

  • 构造过程emplace_back 使用传入的参数直接调用目标类型的构造函数,在容器预分配的内存中构造对象。例如:
    std::vector<std::string> vec;
    vec.emplace_back("hello", 5); // 构造 std::string("hello", 5)
    
  • 内存管理:容器负责管理这些对象的内存,emplace_back 只是将对象“放入”容器,对象的销毁由容器的后续行为决定。

对象销毁的详细时机

以下是 emplace_back 构造的对象在哪些情况下会被销毁的全面分析:

1. 容器销毁时
  • 时机:当容器对象超出作用域或被显式销毁时,容器会调用其析构函数,销毁所有元素,包括通过 emplace_back 添加的对象。
  • 机制:容器的析构函数会逐个调用元素的析构函数(通常按照逆序销毁)。
  • 示例
    #include <vector>
    #include <iostream>
    
    struct MyClass {
        int value;
        MyClass(int v) : value(v) { std::cout << "Constructed: " << value << "\n"; }
        ~MyClass() { std::cout << "Destroyed: " << value << "\n"; }
    };
    
    int main() {
        {
            std::vector<MyClass> vec;
            vec.emplace_back(42); // 构造对象
        } // vec 超出作用域,销毁所有元素
        return 0;
    }
    
    输出:
    Constructed: 42
    Destroyed: 42
    
2. 元素被移除时
  • 时机:当通过容器的方法(如 pop_backerase 等)移除元素时,相应的对象会被销毁。
  • 机制
    • 对于 std::vectorpop_back,最后一个元素被销毁,析构函数被调用。
    • 对于 erase,移除指定位置的元素,其析构函数被调用,剩余元素可能被移动。
  • 示例
    std::vector<MyClass> vec;
    vec.emplace_back(42); // 构造对象
    vec.pop_back();     // 移除并销毁最后一个元素
    
    输出:
    Constructed: 42
    Destroyed: 42
    
3. 容器重新分配内存时(特定于 std::vector 等动态数组容器)
  • 时机:对于 std::vector,如果当前容量(capacity)不足以容纳新元素,emplace_back 会触发内存重新分配。这会导致现有元素被移动(或拷贝,如果没有移动构造函数)到新内存,旧内存中的对象被销毁。

  • 机制

    1. 分配新的更大内存块。
    2. 将现有元素移动到新内存(调用移动构造函数)。
    3. 销毁旧内存中的对象(调用析构函数)。
    4. 在新内存中构造新对象。
  • 示例

    std::vector<MyClass> vec;
    vec.reserve(1);         // 容量为 1
    vec.emplace_back(42);   // 构造对象
    vec.emplace_back(43);   // 容量不足,触发重新分配
    

    输出可能为:

    Constructed: 42
    Constructed: 43
    Destroyed: 42       // 旧对象被销毁(移动后)
    Destroyed: 43       // vec 销毁时
    Destroyed: 42       // 新位置的对象销毁
    
    • 注意:如果类型实现了移动构造函数,销毁的是移动后的“空壳”对象;否则会拷贝并销毁原对象。
  • 避免重新分配:使用 reserve 预分配足够容量,可以避免这种销毁:

    vec.reserve(2); // 预留空间,避免重新分配
    
4. 容器清空时
  • 时机:调用 clear() 会移除并销毁容器中的所有元素。
  • 机制clear() 调用每个元素的析构函数,并将容器大小(size)置为 0。
  • 示例
    std::vector<MyClass> vec;
    vec.emplace_back(42);
    vec.clear(); // 销毁所有元素
    
    输出:
    Constructed: 42
    Destroyed: 42
    
5. 异常情况
  • 构造失败
    • 如果 emplace_back 在构造对象时抛出异常,且对象未构造完成,则不会加入容器,也不会有对象需要销毁。
    • 示例:
      struct MyClass {
          MyClass(int x) {
              if (x < 0) throw std::runtime_error("Negative!");
              std::cout << "Constructed: " << x << "\n";
          }
          ~MyClass() { std::cout << "Destroyed\n"; }
      };
      
      int main() {
          std::vector<MyClass> vec;
          try {
              vec.emplace_back(-1); // 抛出异常
          } catch (...) {
              std::cout << "Exception caught\n";
          }
      }
      
      输出:
      Exception caught
      
  • 内存重新分配失败
    • 如果 emplace_back 触发重新分配,但新内存分配失败(抛出 std::bad_alloc),现有元素保持不变,新对象不会构造完成,不影响已有对象的销毁。

不同容器的差异

  • std::vector:因动态数组特性,可能因重新分配内存导致对象销毁。
  • std::list:基于节点,无重新分配,对象仅在移除或容器销毁时销毁。
  • std::deque:分块存储,emplace_back 通常只影响新块,现有元素不会因重新分配而销毁。

push_back 的对比

  • push_back:传入已有对象(可能是临时对象),将其拷贝或移动到容器中。临时对象在 push_back 后立即销毁,而容器内的对象销毁时机与 emplace_back 类似。
  • emplace_back:直接构造,无临时对象,销毁完全由容器管理。

总结

emplace_back 构造的对象在以下时机销毁:

  1. 容器销毁:超出作用域或析构时。
  2. 元素移除pop_backerase 等操作。
  3. 内存重新分配std::vector 容量不足时,旧对象被移动后销毁。
  4. 容器清空clear() 调用时。
  5. 异常:构造失败不影响已有对象,重新分配失败保持状态。
### 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、付费专栏及课程。

余额充值