须知 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)。