右值引用与移动语义,以及其在g++下的编译

1 篇文章 0 订阅

《linux下的C++ 》课上学习了右值引用移动语义,在此mark 一下。

  1. 背景
    右值引用和移动语义是C++11标准的新特性,在传统的C++标准中没有这一特性。加入这一特性有助于解决函数的参数传递为临时对象时,重复进行临时对象的拷贝消耗时间的问题。

    2.左值与右值
    左值用于标识非临时对象或非成员函数的表达式,是一个表示数据的表达式(如变量名或解除引用的指针),程序可以获得其地址。
    右值:用于标识临时对象的表达式或与任何对象无关的值(纯右值),或者用于标识即将失效的对象的表达式(失效值) 右值包括字面(比如“abc”,1,true)常量。(C风格字符串除外,它表示地址)以及诸如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)。
    传统的C++引用(左值引用)使得标识符关联到左值
    C++11新增了右值引用。右值引用,顾名思义,可以关联到右值,即——可以出现在赋值表达式的右边,但不能对其应用地址运算符的值。 右值引用用符号&&表示。如:

int x = 10, y = 23;//不是引用没关系
int & r = x + y;//非法,实测编译器报错
int && r2 = x + y;//合法
double && r3 = std::sqrt(2.0);

3.移动语义
这里写图片描述
这里写图片描述
这里写图片描述
函数返回时的移动语义:
例如有一个函数返回一个临时变量,此时编译器做的工作就是,创建一个该临时对象的副本,然后将临时对象销毁,再将该临时对象的副本赋值给一个普通对象,然后销毁该副本。而移动语义的做法是:直接将临时对象的所有权交给该普通对象。
函数调用时的移动语义:
相当于完成赋值:
比如

void foo(A& f);//函数1
void foo(A&& f);//函数2
A a1;
A a2;
foo(std::move(a1+a2));/*A 已重载operator+,std::move是因为防止有时候编译优化将(a1+a2)的右值特性无视而加的,,初学可以无视*/

这时候由于右值不能付给左值引用A& f,因此会调用函数2

4.移动赋值与移动构造
赋值过程和构造过程中一般会产生对象的拷贝,所以直接将临时无名对象的所有权移交给右值引用的变量会更节省时间。下面看老师给的具体例子。

class A
{
public:
  A() : _n(0), _p(nullptr)  {  }
  explicit A( int n ) : _n(n), _p(new int[n])  {  }
  A( int n, int * p ) : _n(n), _p(p)  {  }
  A( A && that );
  A & operator=( A && that );
  virtual ~A() {  if(_p){  delete[] _p, _p = nullptr;  }  }
public:
  int & operator[]( int i );
  const int & operator[]( int i ) const;
private:
  int _n;
  int * _p;
};


A::A( A && that )
{
  //  nullptr:C++11预定义的空指针类型nullptr_t的常对象
  //  可隐式转换为任意指针类型和bool类型,但不能转换为整数类型,以取代NULL
  _n = that._n,  _p = that._p,  that._n = 0,  that._p = nullptr;
  //  *this = that;    //  此代码不会调用下面重载的赋值操作符函数
  //  具名右值引用that在函数内部被当作左值,不是右值
  //  匿名右值引用才会被当作右值;理论上如此……
  //  *this = static_cast<A &&>( that );    //  等价于 *this = std::move( that );
  //  上一行代码可以调用下面重载的移动赋值操作符,但是有可能导致程序崩溃
  //  因this指向的本对象可能刚刚分配内存,_p字段所指向的目标数据对象无定义
}
A & A::operator=( A && that )
{
   if( _p )    delete[] _p;    //  删除此行代码可能会导致内存泄露
  //  可以测试是否为同一对象,以避免自身复制操作,但意义不大
  _n = that._n,   _p = that._p,  that._n = 0,  that._p = nullptr;
  return *this;
}

5.实验
主函数:

#include <iostream>
#include "frac.h"
using namespace std;

int main()
{
    frac frac1,frac2;
    inputFrac(frac1,1);
    inputFrac(frac2,2);
    frac frac3;
    frac3=frac1+frac2;//该步输出信息 (in moving setvalue)
    frac frac4(frac1*frac2);//该步理论上输出(in moving constructor)实际未输出
    frac fracx(std::move(frac1*frac2))//该步输出(in moving constructor)
    frac&& fracy(std::move(frac1*frac2))//也不调用移动构造函数,思考为什么
    cout<<endl<<endl<<"sum:  "<<frac3<<"  product:  "<<frac4<<endl;
    return 0;
}
这里写代码片

遇到了一点坑
1.移动语义是C++标准11的,我的编译器codeblocks10.5不支持C++11,又不想卸了重装,于是在shell里编译运行
g++,gcc版本都是4.7 (Note: 有帖子说4.6以上才支持c++11),关于更新g++编译器,请移步:
https://blog.csdn.net/xiaojiewang1990/article/details/65631136

更新完后
在shell里编译c++的方法
cd 到.cpp所在的目录

g++ -std=c++11 -o [filename.out] main.cpp xxx.cpp xxx.cpp xxx.h

xxx@ubuntu:~/code/hw/hw101$ g++ -std=c++11 -o a.out main.cpp frac.cpp frac.h

之后会生成可执行文件 filename.out (比如默认是a.out)

xxx@ubuntu:~/code/hw/hw101$ ./a.out

就会在shell里看到你的运行结果啦。

6.编译优化
有时候你可能发现事情并不想你想的那样,理论上 A&& a3(a1+a2); 会进入移动构造函数。但是并没有。这是因为c++编译器做了编译优化。详情移步:
https://blog.csdn.net/zwvista/article/details/6845020

参考文献:
1.《linux 下的c++》课件,乔林,清华大学
2.https://blog.csdn.net/xiaojiewang1990/article/details/65631136
3.https://blog.csdn.net/zwvista/article/details/6845020
4.https://blog.csdn.net/shujh_sysu/article/details/52029072

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值