首先我们先明白一个概念:什么是左值?什么是右值?
- 左值:既能出现在等号左边也能出现在等号右边的变量或表达式
- 右值:因为声明结束后会被销毁,所以不能放在等号左边
c中,左值其实就是有名字的变量,而运算操作(加减乘除,函数返回值等)就是右值,右值不允许被修改
c++中,相对于右值引入了一个新的概念,基础类型右值不允许被修改,但用户自定义的类型,右值可以通过它的成员函数进行修改
类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低。
class People {
public:
People(string name) 按值传入字符串,可接收左值、右值。接收左值时为复制,接收右值时为移动
: name_(move(name)) 显式移动构造,将传入的字符串移入成员变量
{
}
string name_;
};
People a("Alice"); 移动构造name
string bn = "Bob";
People b(bn); 拷贝构造name
int a = 3;
int b = 2; //此时a b都是左值
int c = a+b;//此时a+b就是右值,a+b产生了临时的变量,在表达式结束之后就会被销毁
那么什么是&,什么是&&
&是c++里的左值引用
&&是c11里的右值引用
左值引用就不多说了,现在解释一下右值引用(来自知乎[hggg ggg])
string a = string("w")+"o"+"r"+"l"+"d"; -----1
//先看一下c11里的string的重载+
string operator+ (const string&& lhs, const string&& rhs);
//而c98里的string重载+为
string operator+ (const string& lhs, const string& rhs);
//c98在执行1这条语句的时候
//因为不能在原本w的基础上进行+(为了不把w的值修改)
//编译器会开辟一个新的内存来存储临时对象
//c11执行1这条语句的时候
//因为右值在语句执行结束后就会被销毁
//所以直接在原本w的基础上进行+,而不用新建临时对象
从这里可以看出,右值引用有利于减少开销
关于右值的生命周期
string Proc()
{
return string("abc");
}
int main()
{
const string& ref = Proc();
//此时右值的生命周期延长了,直到main函数结束
cout << ref << endl;
return 0;
}
- 右值只能被const引用指向,在这时,右值的生命周期被延长了,直到引用销毁。
- 因为右值只能被const引用指向,所以我们才会在拷贝构造函数和赋值函数形参内加上const(还有一个原因是避免参数被修改),这里c11出现了一个特殊智能指针的non const拷贝构造函数
class auto_ptr
{
public:
auto_ptr(auto_tr& p)
{
ptr_ = p.ptr_;
p.ptr_ = NULL;//因为需要修改p的值,所以不能用const
}
private:
void* ptr_;
};
auto_ptr("h"+"i");//编译出错,因为"h"+"i"生成的临时变量不能指向non const参数
如何使上面的临时变量能够被non const引用指向?
使用std::move()接受一个参数,返回该参数对应的右值引用
move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr1赋值或销毁它外,我们将不再使用它在调用move后,我们不能对移后源对象的值做任何假设。我们可以销毁一个移后源对象,也可以赋予它新值,但是不能使用一个移后源对象的值。
//下面是move的源码
template<typename _Tp>
inline typename std::remove_reference<_Tp>::type&& move(_Tp&& __t)
{
return static_cast<typename std::remove_reference<_Tp>::type&&>(__t);
}
//所以我们只需要将上面的要传入auto_ptr的参数先经过move转换成右值引用
//下面有一个move使用的小例子
void swap(T& a, T& b)
{
T tmp = move(a);
a = move(b);
b = move(tmp);
//可以看出相比较之前的进行了多次资源拷贝、销毁的swap
//在swap里使用move只是进行了三次的指针交换,效率提升
}
move源码中使用了static_cast,它是什么?
static_cast是一个强制类型转换符,强制类型转换会告诉编译器:我们知道并且不会在意潜在的精度损失。
如果没有使用强制类型转换符,那么编译器会产生警告
static_cast最神奇的地方在于它可以找回存在于void * t 中的值,如下
double a = 3.14;
void *pv = &a;
//我们不知道void的地址中存放的是什么类型的对象
//对于void而言,它的内存空间仅仅是内存空间,无法访问内存空间里的对象
double *b = static_cast<double*> (pv);
//强制转换的结果与原始的地址值相等
了解了什么是move,那么forward()是什么?
forward()接收一个参数,返回该参数本来所对应的类型的引用。(即完美转发)
void outer(T&& t) {};
void fun(){};
outer(fun());
//fun为右值,但是因为outer的参数有参数名,所以在outer内部,它永远是左值
//为了解决这个问题,就提出了forward()函数(完美转发)
//forward()的源码
template<typename _Tp> inline _Tp&& forward(typename std::remove_reference<_Tp>::type& __t)
{
return static_cast<_Tp&&>(__t);
}
template<typename _Tp> inline _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t)
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
如何使用forword呢?
struct X {};
void inner(const X&) {cout << "inner(const X&)" << endl;}
void inner(X&&) {cout << "inner(X&&)" << endl;}
template<typename T>
void outer(T&& t) {inner(forward<T>(t));}
int main()
{
X a;
outer(a);
outer(X());
inner(forward<X>(X()));
}
//inner(const X&)
//inner(X&&)
//inner(X&&)
网友评论