如何理解std::move和std::forward
首先我们要知道两个点:
- std::move不会进行任何移动
- std::forward不会进行任何转发
听起来很扯是吧,和语义完全不符,他们两者,在运行期都不干任何事的
std::move和std::forward都只是做了一个强制性的型别转换,不同的是std::move是无条件的将实参强行转换为右值,std::forward则只是在传入的形参绑定到右值时进行一个强制型别转换
我们直接看std::move的实现实例代码(c++11)1
2
3
4
5
6template <typename T>
typename std::remove_reference<T>::type&&
move_(T&& t) {
using ReturnType = typename std::remove_reference<T>::type&&;
return static_cast<ReturnType>(t);
}
我们来分析一下这段代码:
- 形参:一个万能引用(T&&),指涉到某个对象的引用
- 返回值:返回值的&&表面其返回的是一个右值引用
此处我们需要明确一下万能引用的推导,如果传进来的实参是左值引用,那么T&&也成了一个左值引用,为了避免这种情况,我们使用std::remove_reference<T>
应用于T,保证我们返回的右值引用是作用在一个非引用型别上面的,确保我们返回的是右值引用。
综上所述,std::move只是强制把形参转换为了右值,如果采用c++14,我们可以更简洁的编写这段代码1
2
3
4
5template <typename T>
decltype(auto) move_(T&& t) {
using ReturnType = typename std::remove_reference_t<T>&&;
return static_cast<ReturnType>(t);
}
注意:如果你想某个对象有执行移动能力的操作,那就不要将其声明为const
例如我们有个一个含有std::string成员变量的类:1
2
3
4
5
6
7class X {
public:
X(const std::string str) : s(std::move(str)) {}
private:
std::string s;
};
你可能回想,这不跑起来了嘛代码,但是你想使用std::move省略对象在传递间减少拷贝的操作完全无效,它依然调用的是复制构造函数。str是被转换为了一个右值,但是在转换之前,const std::string是个左值,转换后,其依然是个左值const std::string,常量性被保留了下来。
再来看一下std::forward,我们举个例子看一下其常用做法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20void func(const std::string& lvalue) {
std::cout << "left value" << std::endl;
}
void func(std::string&& rvalue) {
std::cout << "right value" << std::endl;
}
template <typename T>
void call_func(T&& t) {
func(std::forward<T>(t));
}
void test()
{
std::string s{ "123" };
call_func(s);
call_func(std::string{ "456" });
}
结果1
2left value
right value
std::forward做了什么?他把用来初始化t的实参(也就是要传递给func的实参)是个右值的条件下,把t强制转换为右值类型。因此,仅当实参是使用右值完成初始化时,它才会执行向右值型别的强制型别转换
参考:《Effective Modern C++》条款23