C++复习

C11新特性右值引用&&

2018-04-28  本文已影响112人  凉拌姨妈好吃
首先我们先明白一个概念:什么是左值?什么是右值?

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;
  }
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&&)  
上一篇下一篇

猜你喜欢

热点阅读