完美转发forward、emplace_back减少内存拷贝和移动

一、forward()

int&& a =  10;
int&& b = a;//error

上述中a是一个右值引用,但其本身a也有内存名字,所以a的本身是一个左值。因此,我们有了std::forward()完美转发,类似于T&& val中的val是左值,使用std::forward (val),
就会按照参数原来的类型转发。

int&& a =  10;
int&& b = std::forward<int>(a);//true
#include <iostream>
using namespace std;
template <class T>
void Print(T &t)
{
cout << "L" << t << endl;
}
template <class T>
void Print(T &&t)
{
cout << "R" << t << endl;
}
template <class T>
void func(T &&t)
{
Print(t);
Print(std::move(t));
Print(std::forward<T>(t));
}
int main()
{
cout << "-- func(1)" << endl;
func(1);
int x = 10;
int y = 20;
cout << "-- func(x)" << endl;
func(x); // x本身是左值
cout << "-- func(std::forward<int>(y))" << endl;
func(std::forward<int>(y)); //
return 0;
}

运行结果如下:
-- func(1)
L1
R1
R1
-- func(x)
L10
R10
L10 按照原来的属性转发
-- func(std::forward(y))
L20
R20
R20

解释:
func(1) :由于1是右值,所以未定的引用类型T&&v被一个右值初始化后变成了一个右值引用,但是在func()函数体内部,调用PrintT(v) 时,v又变成了一个左值(因为在std::forward里它已经变成了一个具名的变量,所以它是一个左值),因此,示例测试结果第一个PrintT被调用,打印出“L1"调用PrintT(std::forward(v))时,由于std::forward会按参数原来的类型转发,因此,它还是一个右值(这里已经发生了类型推导,所以这里的T&&不是一个未定的引用类型,会调用void PrintT(T&&t)函数打印 “R1”.调用PrintT(std::move(v))是将v变成一个右值(v本身也是右值),因此,它将输出”R1"func(x)未定的引用类型T&&v被一个左值初始化后变成了一个左值引用,因此,在调用PrintT(std::forward(v))时它会被转发到void PrintT(T&t)。

二、emplace_back 减少内存拷贝和移动
考虑这样的语句:

vector<string> testVec;
testVec.push_back(string(16,'a'));

底层实现:

  • 首先,string(16,‘a’)创建一个string类型的临时对象,此时设计到一次string构造函数
  • 其次,vector内会创建一个string对象,这是第二次构造
  • 最后,在push_back结束时,最开始的临时变量会被析构掉。因此涉及到两次构造和一次析构。

C++11中可以使用emplace_back来代替push_back,emplace_back可以直接在vector中构建一个对象,而非创建一个临时对象,再放进vector,再销毁。emplace_back可以省略一次构建和一次析构,从而达到优化的目的。

//第一种方式
std::string temp("ceshi");
v.pusn_back(temp);// push_back(const string&),参数是左值引用
//第二种方式
std::string temp("ceshi");
v.push_back(std::move(temp));// push_back(string &&), 参数是右值引用
//第三种方式
v.push_back(std::string("ceshi"));// push_back(string &&), 参数是右值引用
//第四种方式
v.push_back("ceshi");// push_back(string &&), 参数是右值引用
//第五种方式
v.emplace_back("ceshi");// 只有一次构造函数,不调用拷贝构造函数,速度最快

总结:
第1中方法耗时最长,原因显而易见,将调用左值引用的push_back,且将会调用一次string的拷贝构造函数,比较耗时,这里的string还算很短的,如果很长的话,差异会更大。
第2、3、4中方法耗时基本一样,参数为右值,将调用右值引用push_back,故调用string的移动构造函数,移动构造函数耗时比拷贝构造函数少,因为不需要重新分配内存空间。
第5中方法耗时最少,因为emplace_back只调用构造函数,没有移动构造函数,也没有拷贝构造函数。


版权声明:本文为weixin_43199441原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。