STL emplace_back

一 介绍

C++11开始,STL的一些容器中增加了emplace和emplace_back成员函数,例如 std::list 和 std::vector。

 以 std::vector::emplace_back举例:

template< class... Args >
void emplace_back( Args&&... args ); (C++11 起) (C++17 前)
template< class... Args >
reference emplace_back( Args&&... args ); (C++17 起)

从C++17开始,改变了返回值,返回被插入元素的引用。

emplace_back 添加新元素到容器尾。元素通过 std::allocator_traits::construct 构造,它典型地用布置 new 于容器所提供的位置原位构造元素。参数 args... 以 std::forward<Args>(args)... 转发到构造函数。

二 emplace_back 和 push_back

简单来说,emplace_back是在容器所提供的位置直接构造,而push_back添加一个新元素到vector末尾,然后拷贝或者移动到新元素。

#include <iostream>
#include <vector>

class A {
 public:
  A() {
    std::cout << "constructor" << std::endl;
  }
  A(int n) {
    std::cout << "constructor int" << std::endl;
  }
  A(const A& a) {
    std::cout << "copy constructor" << std::endl;
  }
  ~A() {
    std::cout << "destructor" << std::endl;
  }
};

int main() {
  A a;
  {
    std::vector<A> vc;
    vc.reserve(10); // 排除扩容干扰
    std::cout << std::endl;
    std::cout << "push_back:" << std::endl;
    vc.push_back(1);
    vc.push_back(a);
  }

  {
    std::vector<A> vc1;
    vc1.reserve(10);
    std::cout << std::endl;
    std::cout << "emplace_back:" << std::endl;
    vc1.emplace_back(1);
    vc1.emplace_back(a);
  }

  std::cin.get();
  return 0;
}

在push_back中先用参数1构造一个临时的A实例,然后拷贝到vector,最后析构临时的A实例;而emplace_back仅用参数1原位构造了一个A实例。对于插入a,表现一致,都是调用A的拷贝构造函数。

所以在支持emplace_back的场景下,推荐用emplace_back代替push_back。

三 补充(2020.10.16)

以 std::list push_back 为例,定义如下:

// std::list<T,Allocator>::push_back
void push_back( const T& value ); (1)	
void push_back( T&& value ); (2)(since C++11)

可以看出,C++11开始,push_back 也增加了移动元素的形式,也就是说:

std::vector<A> vc;

// 等同, 均调用A的移动构造函数
{
  A a;
  vc.push_back(std::move(a));
}
{
  A a;
  vc.emplace_back(std::move(a));
}

为了进一步说明,将二中的例子进行了修改,如下:

#include <iostream>
#include <vector>

class A {
 public:
  A() { std::cout << "constructor" << std::endl; }
  A(int n) { std::cout << "constructor int" << std::endl; }
  A(const A& a) { std::cout << "copy constructor" << std::endl; }
  A(A&& a) { std::cout << "move constructor" << std::endl; } // 增加移动构造函数
  ~A() { std::cout << "destructor" << std::endl; }
};

int main() {
  {
    std::vector<A> vc;
    vc.reserve(10);  // 排除扩容干扰
    std::cout << std::endl;
    std::cout << "push_back:" << std::endl;
    vc.push_back(1);
    {
      std::cout << "------------------------" << std::endl;
      A a;
      vc.push_back(a); 
    }
    {
      std::cout << "------------------------" << std::endl;
      A a;
      vc.push_back(std::move(a)); 
    }
  }

  {
    std::vector<A> vc1;
    vc1.reserve(10);
    std::cout << std::endl;
    std::cout << "emplace_back:" << std::endl;
    vc1.emplace_back(1);
    {
      std::cout << "------------------------" << std::endl;
      A a;
      vc1.emplace_back(a);
    }
    {
      std::cout << "------------------------" << std::endl;
      A a;
      vc1.emplace_back(std::move(a));
    }
  }

  std::cin.get();
  return 0;
}

结果如下:

push_back:
constructor int
move constructor  // 与上例不同,原因见下
destructor
------------------------
constructor
copy constructor   
destructor
------------------------
constructor
move constructor
destructor
destructor
destructor
destructor

 

emplace_back:
constructor int
------------------------
constructor
copy constructor
destructor
------------------------
constructor
move constructor
destructor
destructor
destructor
destructor

* 原因:在C++11中,拷贝构造/赋值 和 移动构造/赋值 必须同时提供或者同时不提供,才能保证类同时具有拷贝和移动语义,只声明一种的话,类仅能实现一种语义。参见 C++11 右值引用(2)移动构造和移动赋值函数

### 关于 `NULL` 和 C++ 中容器操作的理解 #### NULL 的本质解析 在C语言中,`NULL` 被定义为整数值0或者 `(void*)0`。对于指针而言,`NULL` 表示的是一个特殊的值——即零地址(通常写作 `0x00000000`),它用来表明该指针不指向任何有效的对象或函数[^1]。 而在C++标准库宏定义里,则有条件地将 `NULL` 定义成纯量常数0或者是类型转换后的空指针`(void *)0`,这取决于编译环境是否支持C++特性[^2]。 需要注意的是,在现代编程实践中推荐使用nullptr而不是传统的NULL宏定义,因为前者提供了更好的类型安全性和语义清晰度。 #### emplace_back 方法介绍 `emplace_back()` 是STL容器类中的成员方法之一,用于直接在容器内部构造新元素并将其添加到容器末尾的位置。相比于先创建临时对象再调用push_back()的方式来说,这种方式可以减少一次拷贝/移动操作的成本,从而提高性能效率。 以下是使用 `std::vector<int>` 来演示如何利用 `emplace_back()` 插入数据: ```cpp #include <iostream> #include <vector> int main(){ std::vector<int> vec; // 使用 emplace_back 添加元素 vec.emplace_back(42); for(auto& elem : vec){ std::cout << elem << "\n"; } } ``` 此代码片段展示了向动态数组vec追加了一个整型数值42的过程,并通过范围for循环打印出来。 #### 如何弹出元素 要移除位于容器末端的数据项,可以根据具体使用的容器选择合适的方法: - 对于序列式容器如 vector, deque 或者 list 可以采用 pop_back() ```cpp myVector.pop_back(); // 移除最后一个元素 ``` - 如果是关联式容器 set/multiset/map/unordered_map 则应考虑 erase() 请注意上述操作均不会返回被删除的对象副本;如果需要获取即将销毁的那个实例的话,应该事先保存一份引用或复制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值