modern C++中提到使用考虑使用置入代替插入,个人理解就是使用考虑使用emplace_back
代替push_back
操作,那么这两者之间究竟有什么区别,想探究一下
首先这两个函数的定义是不一样的
函数定义
push_back
是有两个函数的(重载),一个接受左值一个接受右值,并且接受右值后进行了move
1 2 3 4 5 6 7 8
| _CONSTEXPR20 void push_back(const _Ty& _Val) { _Emplace_one_at_back(_Val); }
_CONSTEXPR20 void push_back(_Ty&& _Val) { _Emplace_one_at_back(_STD move(_Val)); }
|
而emplace_back
是只有一个函数,是一个模板函数,参数是一个通用引用并且是变长参数,然后进行了完美转发forward
1 2 3 4 5 6 7 8 9 10 11
| template <class... _Valty> _CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) { _Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...); #if _HAS_CXX17 return _Result; #else (void) _Result; #endif }
|
此外,push_back
和emplace_back
都使用了_Emplace_one_at_back进行插入
差异
性能上
先说结论:
理论上来说,emplace_back
比push_back
效率更高。emplace_back能够在vector
内部构建元素,从而减少拷贝或者移动操作
这句话怎么理解呢,举个例子
1 2
| std::vector<std::string> vs; vs.push_back("xyzzy");
|
通过上面的源码我们可以看到push_back
接受的参数是一个T
的元素,但是这里传入的是字面量,所以在这里会通过字面量创建出一个临时变量(隐式转换),等价于下面的代码
1
| vs.push_back(std::string("xyzzy"));
|
综上vs的push_back
总共有三个操作
- 一个
std::string
的临时对象从字面量“xyzzy
”被创建。这个对象没有名字,我们可以称为temp
。temp
的构造是第一次std::string
构造。因为是临时变量,所以temp
是右值。
temp
被传递给push_back
的右值重载函数,绑定到右值引用形参_Val
。在std::vector
的内存中一个_Val
的副本被创建。这次构造——也是第二次构造——在std::vector
内部真正创建一个对象。
- 在
push_back
返回之后,temp
立刻被销毁,调用了一次std::string
的析构函数。
当我们使用emplace_back
时,
1
| vs.emplace_back("xyzzy");
|
emplace_back
使用完美转发将”xyzzy”传入了vector内部(就是前面_Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);
),直接在内部的数组的末尾构建元素插入,减少了临时变量的产生,提高了效率。
接受参数上
emplace_back
使用完美转发,因此只要你没有遇到完美转发的限制(完美转发也会失败,在这里不多讲解),就可以传递任何实参以及组合到emplace_back
。
比如
1 2 3
| vs.emplace_back(50, 'x'); vs.push_back(50, 'x'); vs.push_back(std::string(50, 'x'))
|
再比如下面这种情况,临时变量都不给你转化,只能用emplace_back传入
1 2 3 4 5 6 7 8 9 10 11 12 13
| class A { public: explicit A(int a) :m_a(a) {
} private: int m_a; };
std::vector<A> aVec; aVec.push_back(1); aVec.emplace_back(1);
|
所以写起来emplace_back
肯定是更加舒服的,少写好多字母(理论上减少出错)。
代码实验
实验一(emplace_back高效性)
使用push_back
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
| class BaseClass { public: BaseClass(const std::string name) : name_(name) { std::cout << name_ << " constructor called" << std::endl; } BaseClass(const BaseClass& b) :name_(b.name_) { std::cout << name_ << " copy constructor called" << std::endl; }
BaseClass(BaseClass&& b) { name_ = b.name_; b.name_ = b.name_ + " have move"; std::cout << name_ << " move constructor called" << std::endl; } virtual ~BaseClass() { std::cout << name_ << " destructor called" << std::endl; } private: std::string name_; };
int main(int argc, char** argv) { std::vector<BaseClass> bcVec; std::cout << "--------------------------------push_back :" << std::endl; bcVec.push_back(BaseClass("push_back_obj")); std::cout << "--------------------------------destruct:" << std::endl; }
|
运行结果符合预期
使用emplace_back
1 2 3 4 5 6 7 8 9 10
| int main(int argc, char** argv) { std::vector<BaseClass> bcVec; std::cout << "--------------------------------emplace_back :" << std::endl; bcVec.emplace_back("emplace_back_obj"); std::cout << "--------------------------------destruct:" << std::endl; }
|
运行结果
可以看得出来emplace_back少临时变量的构造、移动、销毁操作,效率要高一些
实验二(两者都传入右值)
如果传入右值,push_back 和 emplace_back效率相同,都会有临时变量产生的构造、移动、销毁操作。
push_back传入右值:
1 2 3 4 5 6 7 8
| std::cout << "--------------------------------push_back rvalue:" << std::endl; bcVec.push_back(BaseClass("push_back_rvalue")); std::cout << "--------------------------------destruct:" << std::endl;
|
上面已经展示过了,这里就不多解释了。
emplace_back传入右值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main(int argc, char** argv) { std::vector<BaseClass> bcVec;
std::cout << "--------------------------------emplace_back rvalue:" << std::endl; bcVec.emplace_back(BaseClass("emplace_back_rvalue")); std::cout << "--------------------------------destruct:" << std::endl; }
|
运行结果,可以看出效率没有提高
实验三(两者都传入左值)
如果传入右值,push_back 和 emplace_back效率相同,两者都会调用拷贝构造函数
push_back传入左值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main(int argc, char** argv) { std::vector<BaseClass> bcVec; std::cout << "--------------------------------push_back lvalue:" << std::endl;
BaseClass obj("obj");
bcVec.push_back(obj); std::cout << "--------------------------------destruct:" << std::endl;
}
|
emplace_back传入左值:
1 2 3 4 5 6 7 8 9 10 11 12
| int main(int argc, char** argv) { std::vector<BaseClass> bcVec; std::cout << "--------------------------------emplace_back lvalue:" << std::endl;
BaseClass obj("obj");
bcVec.emplace_back(obj); std::cout << "--------------------------------destruct:" << std::endl;
}
|
参考: