cpp中vector的push_back和emplace_back精简小结
push_back 和 emplace_back
网络上讲这两个操作差异的文章很多,这里仅从使用差异分析。
定义
假设:
- 控制变量:当前vector能够容下push_back和emplace_back的所有元素,没有触发扩容操作。 使用
vector.reserve()
; - push_back和emplace_back操作的对象类型:
- 普通变量、普通变量
- 普通变量、临时变量
- 临时变量、普通变量
- 临时变量、临时变量
实验的类Foo
#include <iostream>
#include <vector>class Foo {
public:// default ctorFoo(int value = 0) : value_(value) {std::cout << "Foo(int value = 0)" << std::endl;}// copy ctorFoo(const Foo& foo) : value_(foo.value_) {std::cout << "Foo(const Foo& foo)" << std::endl;}// move ctorFoo(Foo&& foo) : value_(foo.value_) {foo.value_ = 0;std::cout << "Foo(Foo&& foo)" << std::endl;}// copy assignment Foo& operator=(const Foo& foo) {value_ = foo.value_;std::cout << "Foo& operator=(const Foo& foo)" << std::endl;return *this;}// move assignmentFoo& operator=(Foo&& foo) {value_ = foo.value_;foo.value_ = 0;std::cout << "Foo& operator=(Foo&& foo)" << std::endl;return *this;}// dtor~Foo() {std::cout << "~Foo()" << std::endl;}private:int value_;
};
1. 普通变量、普通变量
int main() {std::vector<Foo> vFoos;vFoos.reserve(2); // 防止vector扩容std::cout << vFoos.capacity() << std::endl;Foo foo(20); // 普通变量std::cout << "----------------------------------" << std::endl;vFoos.push_back(foo); std::cout << "----------------------------------" << std::endl;vFoos.emplace_back(foo);std::cout << "----------------------------------" << std::endl;
}
输出:
2
Foo(int value = 0)
----------------------------------
Foo(const Foo& foo)
----------------------------------
Foo(const Foo& foo)
----------------------------------
~Foo()
~Foo()
~Foo()
分析:
两者目标就是把普通变量foo的信息放到vector上,但是foo可能后续还得用,所以不能强行移动,只能使用拷贝构造函数。
2. 普通变量、临时变量
int main() {std::vector<Foo> vFoos;vFoos.reserve(2); // 防止vector扩容std::cout << vFoos.capacity() << std::endl;Foo foo(20); // 普通变量std::cout << "----------------------------------" << std::endl;vFoos.push_back(foo); std::cout << "----------------------------------" << std::endl;vFoos.emplace_back(1);std::cout << "----------------------------------" << std::endl;
}
输出:
2
Foo(int value = 0)
----------------------------------
Foo(const Foo& foo)
----------------------------------
Foo(int value = 0)
----------------------------------
~Foo()
~Foo()
~Foo()
分析:
可以看见emplace_back是直接在vector管理的堆上内存原地调用构造函数。
3. 临时变量、普通变量
int main() {std::vector<Foo> vFoos;vFoos.reserve(2); // 防止vector扩容std::cout << vFoos.capacity() << std::endl;Foo foo(20); // 普通变量std::cout << "----------------------------------" << std::endl;vFoos.push_back({1}); std::cout << "----------------------------------" << std::endl;vFoos.emplace_back(foo);std::cout << "----------------------------------" << std::endl;
}
输出
2
Foo(int value = 0)
----------------------------------
Foo(int value = 0)
Foo(Foo&& foo)
~Foo()
----------------------------------
Foo(const Foo& foo)
----------------------------------
~Foo()
~Foo()
~Foo()
分析:
push_back先根据传入的实参{1}
调用构造函数以创建一个栈上的临时对象,然后使用移动/拷贝构造函数将其信息放到vector管理的堆上。这里是使用移动还是拷贝构造比较讲究?换位思考,编译器是能用移动就绝不用拷贝。可能在这个实验的类里两个构造函数的工作类似,但是移动构造对于管理堆上内存的类而言是远比拷贝构造轻量的。深拷贝?
4. 临时变量、临时变量
int main() {std::vector<Foo> vFoos;vFoos.reserve(2); // 防止vector扩容std::cout << vFoos.capacity() << std::endl;std::cout << "----------------------------------" << std::endl;vFoos.push_back({1}); std::cout << "----------------------------------" << std::endl;vFoos.emplace_back(1);std::cout << "----------------------------------" << std::endl;
}
输出:
2
----------------------------------
Foo(int value = 0)
Foo(Foo&& foo)
~Foo()
----------------------------------
Foo(int value = 0)
----------------------------------
~Foo()
~Foo()
分析:
个人认为是emplace_back真正彰显性能优势的场景。它只需要做一件事,通过用户提供的实参1
在vector管理的堆上调用类的构造函数即可。而push_back还是避免不了地要构造临时对象,不过它也在尽力优化地调用移动构造而非拷贝构造(如果可以使用移动构造的话)。
总结
- 如果操作的是临时对象,那么这是emplace_back的用武之地(假设不会触发动态扩容)。
- push_back总是会构造临时对象,然后析构它。不过它也在尽力的优化:去使用移动构造函数而非拷贝构造函数。
待优化的地方
- 构造一个较重的类以量化两个操作的优势?