对C++11中的`移动语义`与`右值引用`的介绍与讨论 (13)

在foo(23)的调用中, 参数是int的左值, 所以模板类型T被推导为int, 所以整个模板函数会被实例化为void foo(int&& t), 没有任何毛病, 这个模板函数的实例确实是个接受右值引用类型参数的函数

但在foo(a)的调用中, 参数是int类型的左值, 这里由于模板函数类型推导的一个特殊规则, 模板函数的类型参数T实质上会被推导为int &类型, 而不是int. 现在问题来了: 如果T被推导成了int &, 那么, T&&的意思难道是int& &&吗? 这是什么鬼玩意?

C++中并不存在一种类型, 可以后面带三个&, 真实的T&&被降格为int &, 换句话说, foo(a)调用的那个实例函数, 它其实长这样:

void foo(int & t) { std::cout << t << std::endl; }

这种从int & &&降格到int &的类型推导过程, 被称为collapsed. 这个类型推导过程中的特殊逻辑, 是C++11中另外一个新特性: 完美转发(perfect forwarding)的基石.

那么如果你真的想写一个函数模板, 让它的参数仅接受右值引用, 正确的写法应该怎么写呢? 正确的写法如下:

#include <type_traits> template<typename T> typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type foo(T&&);

下面就是生动的例子:

#include <iostream> #include <type_traits> template<typename T> typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type foo(T&& t) { std::cout << t << std::endl; } int main(void) { foo(23); int a = 2333; foo(a); // <-- 编译失败 return 0; }

编译失败的信息如下(clang 3.4.2):

main.cpp:14:5: error: no matching function for call to 'foo' foo(a); // <-- 编译失败 ^~~ main.cpp:5:25: note: candidate template ignored: disabled by 'enable_if' [with T = int &] typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type ^

这个写法为什么会生效, 是一个比较复杂的问题, 有兴趣的话可以去研究一下头文件<type_traits>中的内容. 这里就不展开了.

std::move的实现

通过上面的陈述, 你明白了在模板参数的类型推导中, 有一个特殊逻辑叫collapsed, 而其实std::move的实现就与这个特性有关, 下面就是std::move的源代码:

template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

上面是原样复制自libstdc++ 4.8.5中的源代码, 将它稍微修整一下, 长这样:

template<typename T> typename std::remove_reference<T>::type&& move(T&& t) { return static_cast<typename std::remove_reference<T>::type&&>(t); }

我们来解读一波:

首先, 由于(T&& t)的参数类型声明, 与collapsed的推断规则, 我们可以知道, move其实可以接受任何类型的参数.

其次, 它的返回值类型是为 typename std::remove_reference<T>::type&&, 其中std::remove_reference<T>::type保证了在入参为int &类型的情况下, 返回值类型是int&&, 即始终保证返回值是右值引用类型

最后, 函数内部的具体实现, 其实就是调用static_cast将入参强转为右值引用. 由于在函数内部, 形参t已经被初始化为一个左值引用, 根据collapsed可知, 它在函数内部是一个左值(如果引发类型推断的实参是int类型, 则形参会被推导为int && t, 但在被初始化后, t就是一个左值引用. 如果引发类型推断的实参是int &类型, 则形参由于collapsed会被推断为int & t, 在被初始化后, t还是一个左值引用). 所以, 这里先用std::remove_reference脱掉引用, 再用&&将其强转为右值引用.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzspdp.html