Effective Modern C++(二)完美转发与移动语义

 

  • 移动语义使编译器有可能用廉价的移动操作来代替昂贵的拷贝操作。正如拷贝构造函数和拷贝赋值操作符给了你控制拷贝语义的权力,移动构造函数和移动赋值操作符也给了你控制移动语义的权力。移动语义也允许创建只可移动(move-only)的类型,例如std::unique_ptrstd::futurestd::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的左右值产生一个相应的对象。

posted @ 2023-06-04 17:21  icecreamjn  阅读(40)  评论(0)    收藏  举报