【移动语义和精准转发系列二】std::move和std::forward

来源:互联网 发布:iphoneairplay连接mac 编辑:程序博客网 时间:2024/05/16 09:12

在我们开始讲解std::move之前,先来介绍一个概念:引用折叠。这个概念仅用于 typedef 和 模板类型参数 中。

在模板世界中,T&& 称为Universal Reference,即通用引用。这与普通函数中形参的&&是不同的,希望不要弄混了。

对于模板函数template <typename T> void func(T&& t),对于左值实参,T&& t 推导出的T为string&;对于右值实参,T&& t 推导出的T为string。

对于左值引用X&,具有传染性,即X& &、X& &&和X&& &都折叠成类型X&;

对于右值引用X&&,只有X&& &&才折叠成X&&。

下面我们来实战一下:

template <typename T>void func(T&& val) {T t = val;t++;if(val == t) { //... }}
如果传入的是一个右值42,此时T的类型为int,此时if判断永远为false;如果传入的是一个左值整型变量,则T为int&,此时,if判断永远是true。

T&&通常用于两种情况:模板转发或模板重载。对于模板重载,通常使用以下方式进行重载:

template <typename T> void func(T&&);template <typename T> void func(const T&);

好,下面进入正题。


一、先来看一下C++标准源码中是怎么实现std::move模板函数的。

//<bits/move.h> <utility>/***  @brief  Convert a value to an rvalue.*  @param  __t  A thing of arbitrary type.*  @return The parameter cast to an rvalue-reference to allow moving it.*/template<typename _Tp>constexpr typename std::remove_reference<_Tp>::type&&move(_Tp&& __t) noexcept{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
其中,用到了std::remove_reference<_Tp>,这是定义在<type_traits>头文件中的。

// <type_traits>  /// remove_referencetemplate<typename _Tp>struct remove_reference{ typedef _Tp   type; };template<typename _Tp>struct remove_reference<_Tp&>{ typedef _Tp   type; };template<typename _Tp>struct remove_reference<_Tp&&>{ typedef _Tp   type; };
根据模板匹配,最终reremove_reference返回的都是脱去引用后的普通类型。所以,std::remove_reference<_Tp>::type&& 一定是该类型的右值引用。虽然不能隐式将一个左值转换为右值引用,但是我们可以显式地使用static_cast将一个左值转换为右值引用。

C++标准规定,对于具名的右值引用,是一个左值;但是,对于一个无名的右值引用,是一个右值。std::move返回的正是一个无名的右值引用,所以是一个右值。这就是强制把一个左值变为右值引用的函数,当然,该函数也可以作用于右值,返回结果仍然是该右值的引用。


此时,肯定有人会说,既然这样,那为什么我要用std::move函数,我直接使用static_cast强转就OK了。是的,虽然我们可以直接使用static_cast进行右值引用强转,但是,使用标准库move函数是容易得多的方式。而且,统一使用std::move使得我们在程序中查找潜在的截断左值的代码变得很容易。


同时,move的名字冲突要比其他标准库函数的冲突频繁得多。而且,因为move执行的是非常特殊的类型操作,所以程序专门修改函数原有行为的概率非常小。对move,其实还有forward来说,冲突很多,但大多数是无意的,这一特点解释了为什么会使用带限定语的完整版本的原因。通过书写std::move而非move,我们就能明确地知道想要使用的是函数的标准库版本。



二、再来看一下C++标准源码中是怎么实现std::forward模板函数的。

//<bits/move.h>  <utility>    // forward (as per N3143)  /**   *  @brief  Forward an lvalue.   *  @return The parameter cast to the specified type.   *   *  This function is used to implement "perfect forwarding".   */  template<typename _Tp>    constexpr _Tp&&    forward(typename std::remove_reference<_Tp>::type& __t) noexcept    { return static_cast<_Tp&&>(__t); }  /**   *  @brief  Forward an rvalue.   *  @return The parameter cast to the specified type.   *   *  This function is used to implement "perfect forwarding".   */  template<typename _Tp>    constexpr _Tp&&    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept    {      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"    " substituting _Tp is an lvalue reference type");      return static_cast<_Tp&&>(__t);    }

与move不同的是,forward必须通过显式模板来调用,forward返回该显式实参类型的右值引用。即:std::forward<T>的返回类型是T&&。显然,如果T是int&,则T&&仍是int&。如果T是int&&,则T&&仍是int&&。从而forward会保持实参类型的所有细节,包括const,因为对于引用来说,const都是底层的,都会予以保留。

0 0
原创粉丝点击