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
#include
#include
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
emplace_back
传递的是 obj ----- 一个左值,所以会调用 MyObject 的复制构造函数;std::move()
,将对象的类型强制转换为了右值,这时就会使用移动构造函数了,在移动构造函数内使用了一个 data(std::move(other.data))
,毕竟都移动构造了,那么肯定数据是需要转移的,所以在这里成员变量的初始化也同时使用了 std::string 的移动构造函数,反应出来的结果就是打印 obj_.data 时的是没有输出的;push_back()
的输入是左值,会先创建一个临时变量,再将其复制到矢量中,因此这时还会多一个构造过程。又因为这个构造过程是在 emplace_back()
内部发生的,会将临时变量看成一个右值,调用移动构造函数。 5 5 5 和 6 6 6 的区别同上,是 MyObject 的成员变量在初始化时,使用的是移动构造函数还是复制构造函数的区别;emplace_back()
和 push_back()
的使用并无差异。还可以注意的一点是,如果 MyObject 没有定义移动构造函数,编译器将使用复制构造函数。如果也没有定义复制构造函数,将根本不允许上述赋值。
还是举这个例子,删去 MyObject(MyObject&& other)
:
#include
#include
#include
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。
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));