C++11 emplace_back 和 push_back
Reference:
1.《C++ Primer Plus》Stephen Prata
C++11 新增的可变参数模板(vatiadic template)
和 函数参数包(parameter pack)
使得可以提供就地创建(emplacement) 方法。这意味着什么呢?与移动语义一样,就地创建旨在提高效率。看下面的代码段:
class Items
{
double x;
double y;
int m;
public:
Items(); //#1
Items(double xx, double yy, int mm); //#2
...
};
...
vector<Items> vt(10);
...
vt.push_back(Items(8.2, 2.8, 3));
调用 insert() 将导致内存分配函数在 vt 末尾创建一个默认 Items 对象(push_back()调用的insert)。接下来,构造函数 Items() 创建一个临时 Items 变量,该对象被复制到 vt 的开头,然后被删除。在 C++ 中,可以这样做:
vi.emplace_back(8.2, 2.8, 3);
方法 emplace_back()
是一个可变参数模板,将一个函数参数包作为参数:
template <class... Args> void emplace_back(Args&&... args);
在这里可以看到,emplace_back()
与 push_back()
的本质区别在于:
- 使用了右值引用;
- 使用了可变参数模板。
上述三个实参(8.2, 2.8, 3)将被封装到参数 args 中。参数 args 被传递给内存分配函数,而内存分配函数将其展开,并使用接收三个参数的 Items 构造函数(#2),而不是默认构造函数(#1)。也就是说,它使用 Items(args…),这里将展开为 Items(8.2, 2.8, 3)。因此,将在矢量中就地创建所需的对象,而不是创建一个临时变量,再将其复制到矢量中(这一点很重要)。
来看一个示例:
#include <iostream>
#include <vector>
#include <string>
class MyObject {
public:
MyObject(std::string value) : data(value) {
std::cout << "Constructor: " << data << std::endl;
}
MyObject(const MyObject& other) : data(other.data) {
std::cout << "Copy Constructor: " << data << std::endl;
}
MyObject(MyObject&& other) : data(std::move(other.data)) {
std::cout << "Move Constructor: " << data << std::endl;
}
std::string data;
private:
};
int main() {
std::vector<MyObject> myObjects;
myObjects.reserve(10);
std::string value1 = "1";
myObjects.emplace_back(std::move(value1)); // 使用 std::move 来移动 value1 的所有权
std::cout << "value1:" << value1 << std::endl;
std::string value2 = "22";
myObjects.emplace_back(value2);
std::cout << "value2:" << value2 << std::endl;
std::string value3 = "333";
MyObject obj(value3);
myObjects.emplace_back(obj);
std::cout << "value3:" << value3 << std::endl;
std::cout << "obj.data:" << obj.data << std::endl;
std::string value4 = "4444";
MyObject obj_(value4);
myObjects.emplace_back(std::move(obj_));
std::cout << "value4:" << value4 << std::endl;
std::cout << "obj_.data:" << obj_.data << std::endl;
std::string value5 = "55555";
myObjects.push_back(std::move(value5));
std::string value6 = "666666";
myObjects.push_back(value6);
std::string value7 = "7777777";
MyObject obj__(value7);
myObjects.push_back(obj__);
std::string value8 = "88888888";
MyObject obj___(value8);
myObjects.push_back(std::move(obj___));
return 0;
}
有以下输出:
Constructor: 1
value1:
Constructor: 22
value2:22
Constructor: 333
Copy Constructor: 333
value3:333
obj.data:333
Constructor: 4444
Move Constructor: 4444
value4:4444
obj_.data:
Constructor: 55555
Move Constructor: 55555
Constructor: 666666
Move Constructor: 666666
Constructor: 7777777
Copy Constructor: 7777777
Constructor: 88888888
Move Constructor: 88888888
- 1 1 1 中对 std::string 使用移动构造函数 ,它将值赋予了在矢量中创建的 MyObject 对象,因此在此打印 value1 的时候无输出,MyObject 对象的创建使用的是初始化构造函数(因为是对string做的move,所以value1没有值了,这时用一个string创建对象,那肯定使用的是初始化构造函数);
- 2 2 2 中在构造 MyObject 对象时使用的也是初始化构造函数,但是 std::string 的使用是复制构造函数,因此这次打印 value2 的时候是有值的(同上,但因为value2在初始化构造函数内使用的是复制构造函数,所以后面的打印是有值的);
-
3
3
3 和
4
4
4 先直接使用初始化构造函数创建了 MyObject,因此均会先打印一个 Constructor: “string_value”。这时对
emplace_back
传递的是 obj ----- 一个左值,所以会调用 MyObject 的复制构造函数; -
4
4
4 中对 obj_ 额外使用了
std::move()
,将对象的类型强制转换为了右值,这时就会使用移动构造函数了,在移动构造函数内使用了一个data(std::move(other.data))
,毕竟都移动构造了,那么肯定数据是需要转移的,所以在这里成员变量的初始化也同时使用了 std::string 的移动构造函数,反应出来的结果就是打印 obj_.data 时的是没有输出的; -
5
5
5 和
6
6
6 均先使用初始化构造函数先创建了一个 MyObject 对象,但由于
push_back()
的输入是左值,会先创建一个临时变量,再将其复制到矢量中,因此这时还会多一个构造过程。又因为这个构造过程是在emplace_back()
内部发生的,会将临时变量看成一个右值,调用移动构造函数。 5 5 5 和 6 6 6 的区别同上,是 MyObject 的成员变量在初始化时,使用的是移动构造函数还是复制构造函数的区别; -
7
7
7 和
8
8
8 同
3
3
3 和
4
4
4,这时
emplace_back()
和push_back()
的使用并无差异。
还可以注意的一点是,如果 MyObject 没有定义移动构造函数,编译器将使用复制构造函数。如果也没有定义复制构造函数,将根本不允许上述赋值。
还是举这个例子,删去 MyObject(MyObject&& other)
:
#include <iostream>
#include <vector>
#include <string>
class MyObject {
public:
MyObject(std::string value) : data(value) {
std::cout << "Constructor: " << data << std::endl;
}
MyObject(const MyObject& other) : data(other.data) {
std::cout << "Copy Constructor: " << data << std::endl;
}
// MyObject(MyObject&& other) : data(std::move(other.data)) {
// std::cout << "Move Constructor: " << data << std::endl;
// }
std::string data;
private:
};
int main() {
std::vector<MyObject> myObjects;
myObjects.reserve(10);
std::string value1 = "1";
myObjects.emplace_back(std::move(value1)); // 使用 std::move 来移动 value1 的所有权
std::cout << "value1:" << value1 << std::endl;
std::string value2 = "22";
myObjects.emplace_back(value2);
std::cout << "value2:" << value2 << std::endl;
std::string value3 = "333";
MyObject obj(value3);
myObjects.emplace_back(obj);
std::cout << "value3:" << value3 << std::endl;
std::cout << "obj.data:" << obj.data << std::endl;
std::string value4 = "4444";
MyObject obj_(value4);
myObjects.emplace_back(std::move(obj_));
std::cout << "value4:" << value4 << std::endl;
std::cout << "obj_.data:" << obj_.data << std::endl;
std::string value5 = "55555";
myObjects.push_back(std::move(value5));
std::string value6 = "666666";
myObjects.push_back(value6);
std::string value7 = "7777777";
MyObject obj__(value7);
myObjects.push_back(obj__);
std::string value8 = "88888888";
MyObject obj___(value8);
myObjects.push_back(std::move(obj___));
return 0;
}
输出为:
Constructor: 1
value1:
Constructor: 22
value2:22
Constructor: 333
Copy Constructor: 333
value3:333
obj.data:333
Constructor: 4444
Copy Constructor: 4444
value4:4444
obj_.data:4444
Constructor: 55555
Copy Constructor: 55555
Constructor: 666666
Copy Constructor: 666666
Constructor: 7777777
Copy Constructor: 7777777
Constructor: 88888888
Copy Constructor: 88888888
经过比较可以发现,在之前所有使用 Move Constructor 的地方,都换成了 Copy Constructor。
Complement
- 很多官方模板类对移动构造函数即移动赋值函数做了定义,如
shared_ptr
:
所以可以这样使用使程序更优雅:__shared_ptr(__shared_ptr&& __r) noexcept : _M_ptr(__r._M_ptr), _M_refcount() { _M_refcount._M_swap(__r._M_refcount); __r._M_ptr = 0; }
std::vector<std::shared_ptr<MyObject>> myObjects; std::shared_ptr<MyObject> obj(new MyObject("000")); myObjects.emplace_back(std::move(obj));