std::move 是一个类型转换器,将左值转换成右值,其实现如下:
template
typename remove_reference::type&& move(T&& t)
{
return static_cast::type&&>(t);
}
std::move 的实现还是挺简单的就这么几行代码,但要理解这几行代码可不容易。下面我们就来对它做下详细分析。
首先我们来看一下 move 的输入参数,move 的输入参数类型称为通用引用类型。什么是通用引用呢?就是它既可以接收左值也可以接收右值。我们来看一下例子:
#include
template
void func(T &¶m)
{
std::cout << "the value is " << param << std::endl;
return;
}
int main(int argc, char *argv[])
{
int a = 123;
auto &&b = 5; //通用引用,可以接收右值
//int &&c = a; //错误,右值引用,不能接收左值
auto &&d = a; //通用引用,可以接收左值
//const auto &&e = a; //错误,加了const就不再是通用引用了
func(a); //通用引用,可以接收左值
func(10); //通用引用,可以接收右值
return 0;
}
通用引用成立条件:一种是 auto,另一种是通过模板定义的 T&&。实际上 auto 就是模板中的 T,它们是等价的。
下面我们就对这段代码做下详细解读。 代码中的 a 是个左值,因为它在内存中会分配空间,这应该没什么异义;b 是通过引用。为什么呢?因为通用引用有两个条件:一,必须是 T&& 的形式,由于 auto 等价于T,所以 auto && 符合这个要求;二,T 类型要可以推导,也就是说它必须是个模板,而 auto 是模板的一种变型,因此 b 是通用引用。通用引用即可以接收左值,也可以接收右值,所以 b = 5 是正确的;c 不是通用引用,因为它不符合 T&& 的形式。所经第三行代码是错误的,右值引用只能接收右值;d 是通用引用,所以给它赋值 a 是正确的;e 不是通用引用,它多了一个 const 已不符合 T&& 的形式,所以给它左值肯定会出错;最后两个函数调用的形参符合 T&&,又因是模板可以进行类型推导,所以是通用引用,因此给它传左值和右值它都能正确接收。
通用引用好强大呀!它既可以接收左值又可以接收右值,它是如何做到的呢?这就要讲讲模板的类型推导了。
模板的类型推导规则还是蛮复杂的,这里我们只简要说明一下,有兴趣的同学可以查一下 C++11 的规范。我们还是举个具体的例子吧:
template
void func(ParamType param);
func(expr);
上面这个例子是函数模板的通用例子,其中 T 是根据 func 函数的参数推到出来的,而 ParamType 则是根据 T 推导出来的。T 与 ParamType 有可能相等,也可能不等,因为 ParamType 是可以加修饰的。我们看下面的例子:
template
void f(T param);
template
void func(T ¶m);
template
void function(T &¶m);
int main(int argc, char *argv[])
{
int x = 10; // x 是 int
int &rr = x; // rr 是 int &
const int cx = x; // cx 是 const int
const int &rx = x; // rx 是 const int &
int *pp = &x; // pp 是 int *
//下面是传值的模板,由于传入参数的值不影响原值,所以参数类型退化为原始类型
f(x); // T 是 int
f(cx); // T 是 int
f(rx); // T 是 int
f(rr); // T 是 int
f(pp); // T 是 int*,指针比较特殊,直接使用
//下面是传引用模板, 如果输入参数类型有引用,则去掉引用;如果没有引用,则输入参数类型就是 T 的类型
func(x); // T 为 int
func(cx); // T 为 const int
func(rx); // T 为 const int
func(rr); // T 为 int
func(pp); // T 是 int*,指针比较特殊,直接使用
//下面是通用引用模板,与引用模板规则一致
function(x); // T 为 int&
function(5); // T 为 int
}
上面代码中可以将类型推导分成两大类:其中类型不是引用也不是指针的模板为一类; 引用和指针模板为另一类。
对于第一类其推导时根据的原则是,函数参数传值不影响原值,所以无论你实际传入的参数是普通变量、常量还是引用,它最终都退化为不带任何修修饰的原始类型。如上面的例子中,const int &
类型传进去后,退化为 int 型了。
第二类为模板类型为引用(包括左值引用和右值引用)或指针模板。这一类在类型推导时根据的原则是去除对等数量的引用符号,其它关键字照般。还是我们上面的例子,func(x)
中 x 的类型为 int&
,它与 T&
放在一起可以知道 T 为 int。另一个例子 function(x)
,其中 x 为 int&
它与 T&& 放在一起可知 T 为 int&
。
根据推导原则,我们可以知道通用引用最终的结果是什么了,左值与通用引用放在一起推导出来的 T 仍为左值,而右值与通用引用放在一起推导出来的 T 仍然为右值。
实际上上面通过模板推导出的 T 与 move 的返回类型息息相关的,要讲明白这一点我们先要把 move 的返回类型弄明白。下面我们就来讨论一下 move 的返回类型:
typename remove_reference::type&&
move 的返回类型非常奇特,我们在开发时很少会这样写,它表示的是什么意思呢?
这就要提到 C++ 的另外一个知识点,即类型成员。你应该知道 C++ 的类成员有成员函数、成员变量、静态成员三种类型,但从 C++11 之后又增加了一种成员称为类型成员。类型成员与静态成员一样,它们都属于类而不属于对象,访问它时也与访问静态成员一样用::
访问。
了解了这点,我们再看 move 的返类型是不是也不难理解了呢?它表达的意思是返回 remove_reference 类的 type 类型成员。而该类是一个模板类,所以在它前面要加 typename 关键字。
remove_reference 看着很陌生,接下来我们再分析一下 remove_reference 类,看它又起什么作用吧。其实,通过它的名子你应该也能猜个大概了,就是通过模板去除引用。我们来看一下它的实现吧。
template
struct remove_reference
{
typedef T type; //定义T的类型别名为type
};
template
struct remove_reference //左值引用
{
typedef T type;
}
template
struct remove_reference //右值引用
{
typedef T type;
}
上面的代码就是 remove_reference 类的代码,在 C++ 中 struct 与 class 基本是相同的,不同点是 class 默认成员是 private,而 struct 默认是 public,所以使用 struct 代码会写的更简洁一些。
通过上面的代码我们可以知道,经过 remove_reference 处理后,T 的引用被剔除了。假设前面我们通过 move 的类型自动推导得到 T 为 int&&,那么再次经过模板推导 remove_reference 的 type 成员,这样就可以得出 type 的类型为 int 了。
remove_reference 利用模板的自动推导获取到了实参去引用后的类型。现在我们再回过来看 move 函数的时候是不是就一目了解了呢?之前无法理解的 5 行代码现然变成了这样:
int &&move(int &&&&t)
{
return static_cast(t);
}
//或
int &&move(int &&&t)
{
return static_cast(t);
}
经上面转换后,我们看这个代码就清晰多了,从中我们可以看到move实际上就是做了一个类型的强制转换。如果你是左值引用就强制转换成右值引用。
上面的代码我们看起来是简单了很多,但其参数 int& &&
和 int && &&
还是让人觉得很别扭。因为 C++ 编译器根本就不支持这两种类型。咦!这是怎么回事儿呢?
到这里我们就要讲到最后一个知识点引用折叠了。在C++中根本就不存 int& &&
、int && &&
这样的语法,但在编译器内部是能将它们识别出来的。换句话说,编译器内部能识别这种格式,但它没有给我们提供相应的接口(语法)。
实际上,当编译器遇到这类形式的时候它会使用引用折叠技术,将它们变成我们熟悉的格式。其规则如下:
int & &
折叠为 int&
int & &&
折叠为 int&
int && &
折叠为 int&
int && &&
折叠为 int &&
总结一句话就是左值引用总是折叠为左值引用,右值引用总是折叠为右值引用。
经过这一系列的操作之后,对于一个具体的参数类型int & a
,std::move 就变成了下面的样子:
int &&move(int &t)
{
return static_cast(t);
}
这一下我们就清楚它在做什么事儿了哈!
转载于:http://avdancedu.com/a39d51f9/
(SAW:Game Over!)