C++ 11 新特性之右值引用与移动

前 6 篇在这里:

这是C++11新特性介绍的第七部分,涉及到左右值引用、移动构造、移动赋值、完美转发等。
不想看toy code的读者可以直接拉到文章最后看这部分的总结。

右值引用

右值是一个行将销毁的值,例如(i * 10)这种表达式的值。新标准中允许通过&&标识定义一个右值引用,将其绑定到一个右值上。但是,一个右值引用变量又是一个左值,因为它是一个变量了嘛。

std::move

std::move函数的作用很简单,就是获得一个左值的右值引用,这样我们就找到了一种途径将一个右值引用绑定到一个左值上。

但是,使用std::move也意味着交出左值的控制权,之后就不能再使用这个左值了,因为使用std::move之后,无法对这个左值做任何保证。

移动构造

新标准中一些内置类型(如string)都实现了移动构造函数。所谓移动构造,就是接受一个右值引用,从而接受该右值引用所引用的对象,而没有实际的大块内存拷贝操作(可以想象成只拷贝了一个指针而不是整块的内存)。调用移动构造函数的关键是要传入一个相应的右值引用,这时上面提到的std::move函数就派上用场了。

调用移动构造函数之后,右值引用所绑定的对象保证可析构可销毁的状态。

定义自己的移动构造函数

上面说到了,移动构造函数的关键是接受一个右值引用,窃取该对象的内容为己所用(不拷贝),并且保证被窃取的对象保持可析构可销毁的状态。那么,我们当然可以定义一个自己的移动构造函数。

一个整型数组的定义如下:

其中各个函数的定义为:

push_back和print_info的定义就不赘述了。

可以看到,在移动构造函数里,只需要窃取指针及其状态,并将右值引用对象的状态重置,即可完成移动构造的操作。

同样的,我们还可以定义移动赋值运算。

值得注意的是,两个移动函数都添加了noexcept标识符。这也是C++11新标准中引入的,用于向标准库指明此函数不会抛出异常,以避免标准库在和我们定义的这个类进行交互时做一些不必要的工作。如果我们不承诺noexcept,那么当标准库容器扩展容量时,就不能调用移动构造函数来移动容器内的现存元素,而只能采取比较耗费资源的拷贝构造函数。

这一部分的测试代码如下:

移动迭代器

新标准中提供了std::make_move_iterator函数用于从普通迭代器获得移动迭代器。对移动迭代器解引用将会获得对应的右值引用,从而方便的对整个容器进行移动操作。

引用折叠规则

当左右引用遇到模板参数的时候,需要用到引用折叠规则来获得最终的模板推断类型和形参类型。

在上述vague_func中,虽然val的类型是T&&,看上去是个右值引用,但是实际上也是可以接受左值引用的类型的。当传入一个左值时,如lref,编译器会推断T = int&而不是T = int。那么这时实际实例化的vague_func实际是:

void vague_func(int& && val)

根据引用折叠规则,除了T&& &&折叠为T&&之外的所有情况均折叠为T&,那么最终vague_func为:

void vague_func(int& val)

因此,vague_func也可以接受一个左值实参。这种引用折叠规则,也是std::move得以实现的基础,有兴趣的读者可以自行去了解下其实现,就一行代码^o^

但是,vague_func的模板类型推断规则,也造成了T类型的不确定(int还是int&?),这给后续的编码也带来了困难。

std::forward

在上述vague_func中,如果传入一个右值,但是val却是一个变量,也就是一个左值。那么如何保持原来实参的类型信息呢,这时需要用到std::forward。

std::forward(val)返回类型是T&&,这时,根据折叠规则,如果实参val是个左值,则返回T&;如果是右值,则返回T&&。

程序输出

程序的整体输出如下:

总结

  1. 新标准中允许通过&&标识定义一个右值引用,将其绑定到一个右值上。
  2. std::move函数的作用是获得一个变量的右值引用。
  3. 移动构造,就是接受一个右值引用,从而接受(窃取)该右值引用所引用的对象,而没有实际的大块内存拷贝操作,并且保证被窃取后的对象可析构可销毁。
  4. 可以定义自己的移动构造函数以及移动赋值运算。
  5. noexcept用于向标准库指明此函数不会抛出异常。声明移动构造函数和移动赋值运算为noexcept以避免标准库在和我们定义的这个类进行交互时做一些不必要的工作。
  6. 新标准中提供了std::make_move_iterator函数用于从普通迭代器获得移动迭代器。对移动迭代器解引用将会获得对应的右值引用,从而方便的对整个容器进行移动操作。
  7. 引用折叠规则,除了T&& &&折叠为T&&之外的所有情况均折叠为T&,主要用于模板类型推断中。
  8. std::forward(val)用于保持实参的左右值信息。

完整代码详见move_and_forward.cpp

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 2 收藏 评论

关于作者:usher2007

游戏开发 C/C++ Python Linux 个人主页 · 我的文章 · 11 ·    

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部