【C++】C++11的新特性 --- 右值引用与移动语义

07-21 1517阅读

【C++】C++11的新特性 --- 右值引用与移动语义

假如生活欺骗了你 不要悲伤,不要心急! 忧郁的日子里须要镇静 相信吧 快乐的日子将会来临 -- 普希金 《假如生活欺骗了你》

C++11的新特性

  • 1 左值与右值
  • 2 左值引用和右值引用
  • 3 引用的意义
  • 4 移动语义
    • 4.1 移动构造与移动赋值
    • 4.2 区分现代写法与移动语义
    • 4.3 实践中落实移动语义
    • 5 万能引用和完美转发
    • Thanks♪(・ω・)ノ谢谢阅读!!!
    • 下一篇文章见!!!

      1 左值与右值

      C++中,一个表达式不是右值就是左值。C语言中:左值可以位于赋值对象的左边,右值则不能。在C++中就没有这么简单了。在C++中的左右值可以通过是否可以取地址来区分:

      1. 左值表示一个占据内存中可识别位置的一个对象,有可能是一个表达式。更进一步地,可以对左值取地址
      2. 右值即不能进行取地址的值或表达式。包括常量,加减乘除等表达式,临时对象。

      PS:左值和右值在内存中都是有地址的,只有左值可以取地址!

      左值包括变量名,解引用的指针的等。下面是比较经典的左值,他们都可以进行取地址操作!

      注意左值引用和右值引用都是左值

      	int a = 1;
      	int* p = &a;
      	int** pp = &p;
      	const int b = 2;
      

      右值一般是常量,表达式,临时变量 ,对于一个常量肯定是无法取地址的!

      &10;
      &(1 + 1);
      &string("111");
      

      当一个对象被作为右值进行使用时,用的是对象的值(内容);用做左值时,实际使用的是对象的身份(在内存中的位置)

      2 左值引用和右值引用

      左值引用就是对左值进行取别名:

      	int a = 1;    		int& ra = a;
      	int* p = &a;		int*& rp = p
      

      右值引用就是对右值进行取别名:

      int&& rra = (1 + 1);
      string&& rrb = string("111");
      string&& rrb = to_string(111);
      

      左值引用不可以给右值取别名,但是const左值引用可以

      const int& ra = 10;
      const string& rb = string("111");
      

      右值引用不可以对左值取别名,但是可以给move后的左值取别名.move也是C++11新加入的特性,我们后面讲。

      3 引用的意义

      在之前,我们使用引用的目的是什么?是为了减少拷贝,提高性能。

      void func1(const string& s);//传引用参数
      string& func2(const string& s);//传引用返回
      

      但是,引用返回也会出现一些问题,比如一个函数返回临时变量的引用,这时就会出错。临时变量的生命周期只在func2函数,func2函数返回一个临时变量的引用,在函数执行结束,临时变量就会进行销毁!右值引用也无法解决生命周期的问题!

      那右值引用的意义在哪里呢???

      我们来看一个情景:(bit::string是自己写的string类,方便看效果)

      bit::string ret1 = bit::to_string(1234);
      

      这个会进行几次深拷贝?

      【C++】C++11的新特性 --- 右值引用与移动语义

      按理来说,应该会会进行两次拷贝构造,首先拷贝构造临时变量,然后ret1在拷贝构造。但是编译器进行了一个优化,实际上只进行了一次拷贝构造。

      当我们把这个表达式分开写,就不会进行优化了,没有办法合二为一

      bit::string ret1;
      ret1 = to_string(1234);
      

      这两种情况的拷贝的代价都挺大,有没有一种简单的解决办法来避免进行深拷贝?

      这里可不能使用左值引用,因为临时变量在该行函数结束就销毁,在主函数里会直接挂掉!使用右值引用会直接报语法错误,因为在to_string中返回值是一个左值,左值是不能被右值引用的!

      如果将该左值进行move()他就可以被右值引用。

      bit::string&& to_string(int val)
      {
      	bit::string str;
      	//...
      	//处理
      	//...
      	return move(str);
      }
      

      这样运行依旧会挂掉,因为右值引用也是别名,**无法解决生命周期的问题!**这里左值引用和右值引用是没有区别的!

      栈桢图是这样的:

      【C++】C++11的新特性 --- 右值引用与移动语义

      编译器优化后会只进行一次拷贝构造ret1,但还是进行了深拷贝!

      所以这个深拷贝的问题无法通过左值引用或者右值引用来解决!所以就有了移动语义!

      4 移动语义

      4.1 移动构造与移动赋值

      C++11中就加入了一个针对右值引用的拷贝构造 — 移动构造!

      PS:左值引用是拷贝构造 ,右值引用是移动构造!— 构成函数重载

      //拷贝构造 --- const 左值可以接收左值和右值
      string(const string& s) :
      	_str(new char[s._capacity + 1])
      	,_size(0)
      	,_capacity(0)
      {
      	cout 
      	cout 
      	swap(s);//直接将亡值的替换就可以
      }
      
      	string s1("1111111111111111111");
      	string s2 = s1;
      	string s3 = move(s1);
      }
      
      public:
          vector
              vector
                  vv[i].resize(i + 1);
                  if(i == 0) {
                      vv[i][0] = 1;
                  }
                  else if(i == 1){
                      vv[i][0] = vv[i][1] = 1;
                  }
                  else{
                      vv[i][0] =  vv[i][i] = 1;
                      for(int j = 1 ;j _next = node;
      			next->_prev = node;
      			_size++;
      		}
      		//....
      		ListNode(T&& x ) :
      			_next(nullptr),
      			_prev(nullptr),
      			_data(move(x))
      		{}
      

      这样经过每一层的move保证了每次传递的都是右值:

      【C++】C++11的新特性 --- 右值引用与移动语义

      我们就得到了想要的结果!!!

      PS:对于内置类型或者Data来中,左值和右值是没有区别的,他们不会涉及到深拷贝的问题!使用涉及深拷贝的自定义类型才会涉及移动构造和移动赋值!

      5 万能引用和完美转发

      这里在介绍一个新语法:完美转发。它也可以上述做到move的效果,甚至更好!我们最好是使用完美转发!

      template
      void PerfectForward(T&& t)
      {
      	Fun(t);
      }
      int main()
      {
      	PerfectForward(10); // 右值
      	int a;
      	PerfectForward(a); // 左值
      	PerfectForward(std::move(a)); // 右值
      	const int b = 8;
      	PerfectForward(b); // const 左值
      	PerfectForward(std::move(b)); // const 右值
      	return 0;
      }
      

      上面的PerfectForward(T&& t)被叫做万能引用(引用折叠),虽然看上去是一个右值引用,但是他可以随机应变:传左值就是左值引用,穿右值就是右值引用。我们验证一下

      【C++】C++11的新特性 --- 右值引用与移动语义

      可以看到,不是对应匹配的。因为还是那个原因:左值引用和右值引用都是左值!!!而引用折叠虽然会帮助我们实例化出:

      void PerfectForward(int&& t)
      {
      	Fun((int&&)t);
      }
      void PerfectForward(int& t)
      {
      	Fun((int&)t);
      }
      void PerfectForward(const int&& t)
      {
      	Fun((const int&&)t);
      }
      void PerfectForward(const int& t)
      {
      	Fun((const int&)t);
      }
      

      但是由于右值引用是左值,所以根本调用不到右值版本!!!

      所以这里如果使用move,行不行呢?我们试试:

      【C++】C++11的新特性 --- 右值引用与移动语义

      move也无法解决这个问题,因为加上move之后,所以实例化的函数也都带有了func(move(t)),所有的都变成了右值,而我们想要的是该左值就是左值,该右值就是右值,所以就有了完美转发 — std::forward(t)

      template
      void PerfectForward(T&& t)
      {
      	func(forward(t));
      }
      

      完美解决:

      【C++】C++11的新特性 --- 右值引用与移动语义

      完美转发就做到了:**完美转发在传参的过程中保留对象原生类型属性!**完美转发是在函数模版里面帮助辅助传参的!

      • 实参传左值,就推导成左值引用
      • 实参传右值,就推导成右值引用

        完美转发本质上类似进行了一次强转!可以简单的这样理解:

        template
        void PerfectForward(T&& t)
        {
        	func((T&&)t);
        }
        

        上面的list也可以将move该成完美转发了!

        Thanks♪(・ω・)ノ谢谢阅读!!!

        下一篇文章见!!!

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]