须知 std::move 不做任何移动, std::forward 不做任何转发。它们在运行的时候什么都没干。 它们两个都只做了强制类型转换 ,std::move 无条件将实参强制转换成右值,std::forward 是有条件的执行强制类型转换。
std::move 我们来看一看 c++11 中 std::move 的示例实现:
1 2 3 4 5 template <typename T>typename std::remove_reference<T>::type&& move (T&& param) { using ReturnType = typename std::remove_reference<T>::type&&; return static_cast <ReturnType>(param); }
这里也和条款 9 呼应,也就是别名声明 (using) 压倒 typedef 的优势:支持模板化!而 typedef 需要结构体来辅助完成同样功能。如果我们要使用 typedef 来实现同样的功能的话,代码如下:
1 2 3 4 5 6 7 8 9 10 template <typename T>struct StructReturnType { typedef typename std::remove_reference<T>::type&& type; }; template <typename T>typename std::remove_reference<T>::type&& move (T&& param) { return static_cast <typename StructReturnType<T>::type>(param); }
如果是 c++14,有了返回值型别推导,实现 std::move 就更加方便:
1 2 3 4 5 6 7 8 9 10 11 12 13 template <typename T>decltype (auto ) move (T&& param) { using ReturnType = std::remove_reference_t <T>&&; return static_cast <ReturnType>(param); } int main () { int x = 42 ; int & x_ref = x; int && rvalue_ref_1 = move (x); int && rvalue_ref_2 = move (x_ref); return 0 ; }
std::move只做强制类型转换,不做移动 。当然右值是可以实施移动的,所以一个对象实施了 std::move 后就告诉编译器该对象可能具备移动的条件。为什么是可能呢?通常情况下的确没有问题,具备移动的条件。看下面这个例子:
1 2 3 4 5 class Entity {public : explicit Entity (std::string tmp) ; };
这里的 explicit 是一个关键字,通常用于类的构造函数声明中,用于阻止隐式类型转换。当一个构造函数被标记为 explicit 时,它告诉编译器不要执行隐式类型转换,只有显式调用时才会使用该构造函数。
Entity 类的构造函数不需要修改 tmp,根据优良传统 “只要有可能使用 const 就使用”,将代码更改成了下面的形式:
1 2 3 4 5 6 7 class Entity {private : std::string value; public : explicit Entity (const std::string tmp) : value(std::move(tmp)){ }};
代码顺利运行,但是 tmp 是被复制近 value 的,而不是移动。std::move(tmp) 后结果是右值 const std::string,常量性保留下来了。 可以浅浅的看一下 string 的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using string = basic_string<char >; basic_string (const basic_string& __str) : _M_dataplus(_M_local_data(), _Alloc_traits::_S_select_on_copy(__str._M_get_allocator())) { _M_construct(__str._M_data(), __str._M_data() + __str.length (), std::forward_iterator_tag()); } basic_string (basic_string&& __str) noexcept : _M_dataplus(_M_local_data(), std::move (__str._M_get_allocator())) { if (__str._M_is_local()) { traits_type::copy (_M_local_buf, __str._M_local_buf, __str.length () + 1 ); } else { _M_data(__str._M_data()); _M_capacity(__str._M_allocated_capacity); } _M_length(__str.length ()); __str._M_data(__str._M_local_data()); __str._M_set_length(0 ); }
可以看到移动构造的函数只能接受非常量的 string 类型的右值引用作为形参。因为指涉到常量的左值引用允许绑定在一个常量右值性别的形参,最终调用的是 string 的复制构造函数(即使 tmp 为右值)。
通过这个例子,我们可以学习到:
如果想取得对某个对象执行移动操作的能力,不要将其声明为常量。
std::move 不能保证强制型别转换的对象具备可移动的能力。
唯一可以确定的,结果是个右值。
std::forward 需要注意的是:传递给 std::forward 的实参型别应当是个非引用型别,因为习惯上它编码的所传递 实参应该是个右值 (参见条款 28)。std::forward 仅仅在特定情况下会实施强制类型转换。最常见的就是某个函数模板取用了万能引用型别作为形参,传递给另一个函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void func (const Entity& left) ;void func (Entity&& right) ;template <typename T>void func2 (T&& param) { func (param); func (std::forward<T>(param)); } { std::string str ("hello" ) ; Entity e (str) ; func2 (e); func2 (std::move (e)); }
我们希望 func2 传入的是一个左值时,执行 func 的左值的版本,传入的是个右值的时候执行 func 重载的右值的版本。但是函数形参都是左值,也就是说这里的 param 一直是左值,不论传入 func2 的是左值还是右值,都只会执行 func 的左值版本。此时,std::forward 就做到了这件事:仅当实参是右值完成初始化的时候才会执行向右值的强制类型转换。
那么 std::forward 是如何知道实参是否通过右值来完成初始化的呢?其实是通过传入的函数模板形参 T 来实现的 (详细参见条款 28)。