你所需要的基础知识

通用引用和右值引用

在实现std::move()以及std::forward()前,我们要对通用引用以及右值引用之间做一个基本的了解。一般来讲,当我们见到&&的时候,我们都会认为这是个右值引用,是用来绑定到右值上一个引用类型。但是如果这种情况出现模板参数的推导时,事情就会发生变化。

1
2
3
4
template <typename T>
void Func(T&& param) {
std::cout << param << std::endl;
}

上面这种情况如果在不知道通用引用的情况下,我们会认为这个函数的参数只能绑定到右值上,因为很明显,&&代表的是右值引用。但是实际上下面的两个调用都可以通过编译并且给出运行结果。

1
2
3
std::string value = "Hello world!";
Func(value);
Func(std::move(value));

也就是说神奇的事情发生了,Func即接受左值,又接收右值,通常来讲我们只会在const&上看到这种情况,但是这里居然也发生了相同的事情。要想理解这种行为,我们就不得不了解一个全新的知识点,也就是引用折叠(reference collapsing)

引用折叠

我们知道,在C++里面,我们是没有办法声明引用的引用的,因为引用实际上是另一个变量的别名,是一种指针的语法糖。要是按照权威的书籍解释的话,也就是C++ primer中所述的

因为引用本身不是一个对象,所以不能定义引用的引用。

不过话虽然是这么说,但是实际上编译器不得不处理这种情况。例如上面的Func(value),实际上如果进行展开的话,编译器会看到这么一个东西。

1
void Func(string& &&param);

这里先忽略掉为什么T为什么会被推导为string&(感兴趣的话可以去看看effective modern C++),我们可以看到在类型推导中出现了左值引用的右值引用。但是又因为是没有引用的引用这一说法,所以编译器就会做一点手脚,来确保程序可以运行下去,而所做的这一手脚也就是所谓的引用折叠。其规则如下:

发生引用的引用情况下,如果任一引用为左值引用,那么结果是左值引用,否则就是右值引用。

好,当我们记住这个之后,我们就可以来实现std::move()以及std::forward()了。

GO! 来进行具体实现吧

std::move()

std::move()所做的事情很简单,就是不管传进来的是什么东西,我都把它给转成右值引用。这个的实现并不需要我们之前所讲的引用折叠。其实现如下

1
2
3
4
5
template<typename T>
decltype(auto) move(T&& param) {
using returnType = std::remove_reference_t<T>;
return static_cast<returnType&&>(param);
}

可以看到就是利用std::remove_reference把参数本身的引用性去了,然后将值类型static_cast到具体的右值引用。

std::forward()

std::forward()做的事就有点不一样了,它可以看作一个有选择的std::move(), 常用于函数参数的转发中。因为虽然我们平时嘴上喊着左值引用,右值引用什么的,但实际上,无论什么参数,它都是左值,而这些引用绑定到左值上还是右值上,与它们本身是左值毫无关系。所以为了保证其能够保持所谓的左值性与右值性,我们才需要std::forward()函数。

它的行为具体表现为,一个函数的实参要是被右值初始化,那么我们就把对应的参数转化为右值引用。代码如下

1
2
3
4
template<typename T>
T&& forward(std::remove_reference_t<T>& parms) {
return static_cast<T&&>(parms);
}

可以看到与std::move()的不同就是最后在static_cast的时候,std::forward()接受的参数是T&&,这样的话,通过引用折叠,我们就可以将参数完美的转发出去。

还是用上面的Func(value)Func(std::move(value))来进行举例子。

1
2
Func(value)-> T==string& -> static_cast<string& &&> -> 折叠! -> static_cast<string&> 大功告成,传进来左值,转化到左值!
Func(value)-> T==string -> static_cast<string&&> 大功告成,传进来右值,转化到右值!

这里T的推导规则要是不懂的话,还是推荐之前的effective modern c++,里面把规则说的很明白了。

引用