Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

emplace_back与push_back

2020/10/25 C++ 防忘系列
Word count: 1,277 | Reading time: 5min

之前看1002. 查找常用字符题解的时候,发现有人用了emplace_back,将char转型成了string塞进了vector<string>,感觉是个骚操作。
之前也看过emplace_back和push_back的区别, 只不过又忘记了, 因此本次也算个防忘系列把…

1
2
3
4
5
6
7
vector<string> ans;
for (int i = 0; i < M; i++) {
for (int j = 0; j < minFreq[i]; j++) {
// emplace_back 骚操作
ans.emplace_back(1, 'a' + i);
}
}

在STL中,进行插入元素的时候,有insert和push两种选择方式,而在有了右值引用和移动语义的时候,在C++11中就提出了更高效的插入方法:emplace_back

目前的趋势是希望:使用emplace_back()取代push_back()

据统计,emplace_back()函数要比push_back()函数要快一倍。

emplace_back

empalce与push的区别:

  • push_back()函数向容器中加入一个临时对象(右值元素)时, 首先会调用构造函数生成这个对象,然后调用拷贝构造函数将这个对象的拷贝放入容器中, 最后释放临时对象,这样造成的问题是临时变量申请的资源就浪费。但是emplace_back()函数向容器中中加入临时对象, 临时对象原地构造,只有转移的过程,没有赋值或拷贝的操作(不需要触发拷贝构造)。

emplace_back中调用构造函数

官网demo Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <vector>
#include <string>
#include <iostream>

struct President{
std::string name;
std::string country;
int year;

// 构造函数
President(std::string p_name, std::string p_country, int p_year)
: name(std::move(p_name)), country(std::move(p_country)), year(p_year){
std::cout << "I am being constructed.\n";
}

// 转移构造函数
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year){
std::cout << "I am being moved.\n";
}

// 拷贝赋值操作符
President& operator=(const President& other) = default;
// 拷贝构造函数
};

int main(){
std::vector<President> elections;
std::cout << "emplace_back:\n";
// noted: 这边并没有写成President("Nelson Mandela", "South Africa", 1994)
elections.emplace_back("Nelson Mandela", "South Africa", 1994);

std::vector<President> reElections;
std::cout << "\npush_back:\n";
reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));

std::cout << "\nContents:\n";
for (President const& president: elections) {
std::cout << president.name << " was elected president of "
<< president.country << " in " << president.year << ".\n";
}
for (President const& president: reElections) {
std::cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
}

▲看到noted标出来的地方后, 就能知道Leetcode题解中的骚操作其实就是根据T类型判断出了调用构造函数。按如下代码得证

1
2
3
string a(1, 'a'+ 1);
cout << a << endl;
// >>> b

引申:什么是std::move?

借鉴:

c++ 之 std::move 原理实现与用法总结

在C++11中,标准库在<utility>中提供了一个有用的函数std::move,std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);

左值、左值引用、右值、右值引用概念

  • 左值的声明符号为”&”, 为了和左值区分,右值的声明符号为”&&”。
  • 临时对象是作为右值处理的

右值引用的意义

直观意义:为临时变量续命,也就是为右值续命,因为右值在表达式结束后就消亡了,如果想继续使用右值,那就会动用昂贵的拷贝构造函数。(关于这部分,推荐一本书《深入理解C11》)
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C
应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。
通过转移语义,临时对象中的资源能够转移其它的对象里。
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。
普通的函数和操作符也可以利用右值引用操作符实现转移语义。

extra:

  1. std::move执行一个无条件的转化到右值。它本身并不移动任何东西;
  2. std::forward把其参数转换为右值,仅仅在那个参数被绑定到一个右值时;
  3. std::move和std::forward在运行时(runtime)都不做任何事。

Author: Mrli

Link: https://nymrli.top/2020/10/18/emplace-back与push-back/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Python中import的细节
NextPost >
AutoLianliankan笔记
CATALOG
  1. 1. empalce与push的区别:
  2. 2. emplace_back中调用构造函数
    1. 2.1. 引申:什么是std::move?
    2. 2.2. 右值引用的意义