Effective Modern C++(二)完美转发与移动语义
- 移动语义使编译器有可能用廉价的移动操作来代替昂贵的拷贝操作。正如拷贝构造函数和拷贝赋值操作符给了你控制拷贝语义的权力,移动构造函数和移动赋值操作符也给了你控制移动语义的权力。移动语义也允许创建只可移动(move-only)的类型,例如
std::unique_ptr
,std::future
和std::thread
。
移动语义的提出是为了解决拷贝的成本太高这一问题。特别是在对一些将亡值或者说右值的拷贝操作上,这些被拷贝的对象属于临时对象,马上就要被析构调或者不再被使用。
在这种情况下,我们完全可以将被拷贝对象在内存中的资源直接移动给新的对象,再将被拷贝对象置空即可。这样做就避免了逐个拷贝内存中资源带来的成本增加问题。
但是也要注意到,这样做的前提是被拷贝对象将要马上消失;或者说不再被访问,使得我们提前将其释放掉也不会产生问题。
为此我们往往用move()函数来将对象转换问右值,以此来提示编译器或程序员,这个对象是可移动的。
-
完美转发使接收任意数量实参的函数模板成为可能,它可以将实参转发到其他的函数,使目标函数接收到的实参与被传递给转发函数的实参保持一致。
光看定义,一定有菜鸡和我一样想问,目标函数实参和转发函数的实参难道会变吗,不是一直一样吗?以此带动产生第二个问题,完美转发的用处在什么地方?
先说第一个问题,当我们想用一个转发函数将对象A转发到目标函数时,对象A首先作为实参初始化了转发函数中的形参,然后再用转发函数中的形参初始化目标函数中的实参。
看到问题所在了吗,就是这个形参。
形参永远是左值,即使它的类型是一个右值引用。
当然,这是很合理的,因为对一个函数中的形参取地址一定是可行的,这也意味着他是个左值。
void func(int &&a){ cout<<"right value"<<endl; } void func(int &a){ cout<<"left value"<<endl; } void fun(int &&a) { func(a); } int main() { fun(1); //left value return 0; }
而这也就意味着一个很可怕的事实,在函数转发参数的过程中,对象的左值或右值性丢失了,完美转发正是为了解决这一问题提出的,保证目标函数收到一个左右值不变的对象。
而为了解决这个问题,一个很自然的想法就是利用通用引用,因为通用引用形参被传入实参时才确定是左值还是右值。
class Widget {
public:
template<typename T>
void setName(T&& newName) //newName是通用引用
{ name = std::forward<T>(newName); }
…
};
利用通用引用,我们将一个左右值不变的对象传入到了std::forward<>()函数中,forward再根据T的左右值产生一个相应的对象。