首先看一个函数声明:
void func(type &&rval);
其中 type
可以是任意类型(并非函数模板参数)。需要注意的是,此函数只能接收一种实参“类型”:右值,注意此类型指的是左值,左值引用,右值,右值引用这四种之一。
以下我们暂时假定type
为std::string
。
这种规定意味着我们只能通过这样:
func(std::string("hello"));//手动构造一个右值传入
或者这样:
std::string s("hello");
func(std::move(s));//使用std::move()强转为右值,注意,是强转为右值!!!
以及这样:
std::string s("world");
func(static_cast<string &&>(s));
来调用此函数。
可能有人会产生疑问:既然func函数函数形参是std::string&&
类型,那么我传入一个相应的右值引用类型不可以吗?
即:
std::string &&rs = std::string("hello world");
func(ss);//error
这似乎与我们的直觉相违背,错误产生的原因有两点:
- 右值引用本身是左值
- 右值引用只能绑定到右值
至于为什么,因为标准就是这么规定的。。。。
而与右值引用相对应的左值引用就没有太多限制,当我们声明一个具有左值引用形参的函数时:
void fun(std::string &s);
//显然其可以接受一个左值:
std::string s_("hehe");
fun(s_);
//同时也可以接受一个左值引用:
fun(ls);
//甚至可以绑定到一个右值引用:
std::string && rs = std::("haha");
fun(rs);
当然,当函数声明为void fun(const std::string &s)
时,亦可以传入一个右值给此函数:
fun(std::string("fuck"));//ok
我们知道,函数传参规则和变量初始化规则相同,所以上述三种情况可以写成:
std::string s("fucccccck");
std::string &lval_1 = s;//左值引用绑定到左值
std::string &lval_2 = lval_1;//左值引用绑定到同类型引用所绑定的左值上
std::string && rval = std::string("ff");
std::string &lval_3 = rval;//情况3
这里情况3将一个右值引用初始化给一个左值引用,那lval_3到底绑定到谁呢?答案是左值引用会绑定到右值引用所绑定的右值对象上。
我们知道非const引用不能绑定到右值对象上:
std::string &lref = std::string("haha");//error
但是以下方式却能间接绑定到右值:
std::string &&rref = std::string("hehehe");
std::string &lref = rref;
std::cout << lref << std::endl;//"hehehe"
lref[0] = 'p';
std::cout << lref << std::endl;//"pehehe"
所以对于初始化一个左值引用,不管是用一个左值,还是一个同类型的左值引用,或者一个右值引用,最终这个左值引用都会绑定到一个对象上。
接下来看一下std::move()和std::forward()函数。
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type &&>(t);
}
让我比较产生疑惑的是move()函数的返回值,当我们传一个左值给move():
std::string s("he");
std::move(s);
函数模板move()的类型参数T会被推断为std::string &
,即move()函数会变成这个样子:
typename remove_reference<std::string &>::type&& move(std::string& &&t)
{
return static_cast<typename remove_reference<std::string &>::type &&>(t);
}
typename remove_reference<T>::type
的作用就是将类型T中的引用去除,还原出原生类型。再通过引用折叠,所以最终move()函数会被实例化为下面这样:
std::string&& move(std::string &t)
{
return static_cast<std::string &&>(t);
}
看似move()函数返回一个右值引用类型,但其实返回的是一个右值,或者说是将左值引用t所绑定的右值返回。文章开头提到的static_cast<std::string &&>效果也是一样。
所以,现在我们知道,对于一个函数,如果其声明为:
retType func(ParaType && t);
则说明该函数只接受右值参数(可以从move()函数和static_cast获得),那什么情况下需要将函数声明为此类型呢?我们知道右值对象是即将要消亡的对象,当函数接受此种参数即是在告诉函数:调用此函数之后的代码不会再使用此对象(或者说不会再使用该对象拥有的资源),所以该函数有权力、责任去管理利用所传进来的右值对象拥有的资源。
那如果是没有拥有任何资源的右值对象被传进来会怎样呢?