move() forward()

含义

moveforward都是C++11中引入的,它们是移动语义和完美转发实现的基石。

  • move:不能移动任何东西,它唯一的功能将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义
    • 从实现上讲,std::move基本等同于一个类型转换static_cast<T&&>(lvalue)
  • forward: 不转发任何东西,也是执行左值到右值的强制类型转换,只是仅在特定条件满足时才执行该转换
    • 典型使用场景:某个函数模板取用了万能引用类型为形参,随后把它传递给了另一个函数

   

那么问题来了,既然move不移动而只是转换类型,为什么还把它命名为move,而不是一个类似rvalue_cast之类的名字?

  • 首先再重复一下,move做的是强制类型转换,把左值转换为右值,不做移动
  • 而右值是可以进行移动的,所以move一个对象,就是告诉编译器该对象具备可移动的条件,这样,move表述了这样一个事实:该对象可以移动了

       

move很牛逼,但可能并没有想象中那么牛逼

移动语义的支持,使得我们可以以较低的成本执行以前只能复制的操作。

对于标准容器操作,如下:

std::vector<Foo> vf1;

   

// 放入数据

   

// 移动

auto vf2 = std::move(vf1); // O(1),仅是包含在vf1vf2中的指针修改了

以上操作得以实现的前提是,标准容器都是将内容放在堆上的,它们内持有一个指向内存的指针。

正是由于该指针的存在,移动操作在常数时间内完成得以实现:把指针从源容器复制到目标容器,然后把源容器包含的指针置空即可。

   

注意,并不是所有move操作都是如此低廉的:

std::array<Foo, 10000> af1;

   

// 放入数据

   

// 移动

auto af2 = std::move(af1); // O(n),需要把af1中所有元素移动到af2

这是因为array对象并没有内置的指针,其内容数据是直接存储在对象内的。

   

所以不要总是相信下面的说法:移动容器和赋值一堆指针一样成本低廉。

   

总之,在下面的情况下,移动语义并不会带来什么好处:

  • 没有移动操作:待移动对象没有提供移动操作,这时移动请求就变成了复制请求
  • 移动不是更快:待移动对象有移动操作,但并不比复制操作更快,如采用了小型字符串优化(SSO)的小string(容器不超过15的字符串)
  • 移动不可用:在移动本可以发生的语境下,要求移动操作不可抛出异常,但该操作未加上noexcept声明
  • 源对象是个左值:除极少数例外,只有右值可以作为移动对象的源

   

move不同,forward仅在特定条件下将实参强制转换为右值。

典型使用场景如下:

void process(const Foo& lval);

void process(Foo && rval);

   

template<typename T>

void logAndProcess(T&& param)

{

        auto now = std::chrono::system_clock::now();

        makeLogEntry("Calling 'process'", now);

        process(std::forward<T> param);

}

  

其中:

  • 形参param被传递给函数process,而process依据形参是左值还是右值进行了重载,实现不同的操作
  • 我们期望,当调用logAndProcess时,传入的是左值,则以左值被process处理,右值也同样传给process处理
  • forward干的就是这个事情:

Foo f;

   

logAndProcess(f); // 传入的是左值,process以左值来处理

logAndProcess(std::move(f)); // 传入的是右值,process以右值处理

也就是说,使用forward可以实现完美转发,但完美转发有时候并不完美,甚至不能转发。

下面是一些需要格外注意的情形:

  • 大括号初始化物,下面两种情况会导致失败:
    • 编译器无法为函数形参推导出类型
    • 编译器为函数形参推导出了"错误"的类型
  • 0NULL用作空指针:它们会被推导为int,而非传递实参的指针类型,应该使用nullptr
  • 仅有声明的整形static const 成员变量:能正常进行函数调用,它们没有内存,因此不能被引用,万能引用自然会失败
  • 重载的函数名字和模板名字:这能通过编译,但转发函数会执行失败,因为它无法决定使用哪个重载的版本
  • 位域用作函数实参:非const引用不得绑定到位域,位域是由机器字的若干任意部分组成的,无法对其直接取址。解决办法是传递位域的副本

   

小结

moveforward在作为c++11中引入的关键特性,使用得当可以达到很多以前很难实现的功效。

但也要注意避免使用上的陷阱,防止只是表面上使用这些特性,却没有收到任何实际的效果。

   

   

   

   

————————————————

版权声明:本文为CSDN博主「guotianqing」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/guotianqing/article/details/115837222

posted @ 2022-08-30 21:45  atomxing  阅读(111)  评论(0编辑  收藏  举报