C++11学习总结5——右值引用

C++11学习总结5——右值引用,第1张

右值引用 右值引用的概念

概念解析

  • 引用:给内存块取别名,C++11之前只能给左值取别名,C++11之后可以给右值取别名
  • 左值(lvalue):出现在=左边的就是左值
  • 右值(rvalue):出现在=右边的值,常常为 字面常量、表达式、函数的非引用返回值等

注意点:右值引用不能绑定左值

右值举例:

int a = 10;	//a是左值,10为右值
string str = "hello";	//str是左值,字面量“hello”是右值
int func(){
	return 100;		//函数非引用返回值100 为右值
}

右值引用语法:用&&代表右值引用

类型 && 变量名 = 右值;

右值引用语法举例

int && a = 10;
string && b = "hello";	
移动语义

作用:在函数的参数值传递时(值的返回)就会发生拷贝构造、析构函数,这对程序的性能会有很大影响。

移动语义就可以减少此类发生。

有点类似于拷贝文件和剪切文件,剪切文件的效率更高。

移动构造函数

C++11新增的构造函数。

在函数中返回非引用类型变量(比如局部变量),编译器先会调用移动构造,如果程序员没有写移动构造才会第调用拷贝构造

语法

class A{
public:
	A(A && rv){ //移动构造,参数是非const右值引用
        this->xx指针 = rv.xx指针;
        rv.xx指针 = nullptr;
    }	
};

详情解析

移动赋值函数

本质:重载operator=,参数是非const 的右值引用。

返回左引用

语法:

class A{
public:
	A& operator=(A && rv){ //=重载,参数是非const右值引用,返回左引用
        if (&rv == *this){
			return *this;
        }
        this->xx指针 = rv.xx指针;
        rv.xx指针 = nullptr;
        return *this;
    }
};

详情解析

范例
class A
{
public:
	A() :array(new int[3]{ 1, 2, 3 })
	{
	}
	~A()
	{
		cout << "调用析构函数" << endl;
		if (nullptr != array)	//析构的时候,要看看指针是否为空,不为空就要释放
		{
			delete[] array;
		}
	}
	A(A &&a)		//移动构造
	{
		this->array = a.array;
		a.array = nullptr;		//一定要让a的指针为空,注意不是delete!
		//a.array = nullptr;	//如果是delete会导致相同内存重复delete,从而崩溃
		std::cout << "移动构造" << endl;
	}
	A(const A& a)	//拷贝构造
	{
		this->array = new int[3]{ a.array[0],a.array[1],a.array[2] };
		std::cout << "拷贝构造" << endl;
	}
	A& operator=(const A& a)	//等号重载
	{
		std::cout << "等号重载" << endl;
		if (&a == this) {
			return *this;
		}
		this->array = new int[3]{ a.array[0],a.array[1],a.array[2] };

		return *this;
	}
    A& operator=(A&& a)	//	移动赋值函数
	{
		std::cout << "移动赋值函数" << endl;
		if (&a == this) {
			return *this;
		}
		this->array = a.array;
		a.array = nullptr;

		return *this;
	}
	static A returnTemObj()
	{
		A a;
		std::cout << "局部变量a的数组地址为 " << a.array << endl;
		return a;	//只要是return非引用类型变量就会调用移动构造
	}

	int *array{ nullptr };
};


void func1() {
	A a1;
	A a2(a1);
}

void func2() {
	A tem = A::returnTemObj(); //即使没有A tem 只有A::returnTemObj();也会触发移动构造
	cout << "tmp的数组地址为" << tem.array << endl;
}

void func3() {
	A a;
	A b;
	b = a;
}

void func4() {
	A a;
	a = A();	//创建一个临时对象,并且赋值给a对象,这就达成了移动赋值
}

int main()
{
	cout << endl << "========拷贝构造=========" << endl;
	func1();
	cout << endl << "========移动构造=========" << endl;
	func2();
	cout << endl << "========等号重载=========" << endl;
	func3();
	cout << endl << "========移动赋值=========" << endl;
	func4();
	return 0;
}
执行结果
========拷贝构造=========
拷贝构造
调用析构函数
调用析构函数

========移动构造=========
局部变量a的数组地址为 012E03E0
移动构造
调用析构函数
tmp的数组地址为012E03E0
调用析构函数

========等号重载=========
等号重载
调用析构函数
调用析构函数

========移动赋值=========
等号重载
调用析构函数
调用析构函数

解析移动构造

在上面例子中的func2调用了步骤如下

  1. 调用类的static A returnTemObj() 函数
  2. 程序进入returnTemObj函数
    1. 创建局部变量a
    2. return a;(因为class A写了移动构造,所以优先执行A的移动构造,而不是执行A的拷贝构造)
    3. a作为右值进入class A的移动构造中(return 非引用的对象,那么这个对象就属于右值)
      1. 将右值a 的array指针赋值给临时对象的array
      2. 将右值a 的array置空
    4. delete 局部变量a。

      (假设在移动构造中右值a 的array没有置空,那么此处会delete1次array)

  3. 给临时对象命名为tmp
  4. returnTemObj函数结束,delete tmp

对比拷贝函数func1的差异:

对比项目func1(拷贝构造)func2(移动构造)
触发时机1、拷贝构造时
2、没有移动构造时的return非引用的对象
return 非引用的对象时
入参对比const A&obj
const修饰的左值引用
A&& obj
无const修饰的右值引用
处理类中指针this->xx指针需要重新new,
并且要把obj.xx指针中所指的数据拷贝到this->xx指针中
this->xx指针直接赋值obj.xx指针
是否需要修改obj不需要需要将obj中的指针置空
(不是delete)
解析移动赋值函数

在上面例子中的func4调用了步骤如下

  1. 创建一个class A类型的对象a
  2. 创建一个class A类型的临时对象
  3. 将临时对象(右值)赋值给a
  4. 触发移动赋值函数,程序进入移动赋值函数体内
    1. 将临时变量的array赋值给a的array
    2. 将临时变量 的array置空
  5. 移动赋值函数结束,临时对象失效被delete
  6. func4函数体结束,a被delete

对比等号重载func3的差异:

对比项目func3(传统的=运算符重载)func4(移动赋值=运算符重载)
入参对比const A&obj
const修饰的左值引用
A&& obj
无const修饰的右值引用
处理类中指针this->xx指针需要重新new,
并且要把obj.xx指针中所指的数据拷贝到this->xx指针中
this->xx指针直接赋值obj.xx指针
是否需要修改obj不需要需要将obj中的指针置空
(不是delete)
返回的数据类型A& (左引用)A& (左引用)
std::move 左值变右值

作用:

  • C++11中右值引用不能绑定左值。

    但是使用move就可以将左值转成右值。

  • 右值才能使用移动构造和移动赋值函数,如果左值想要执行移动赋值就需要先转成右值

语法:对于普通左值变量 转 右值变量

int a = 10;
int && b = std::move(a);
std::forward完美转发

可以将参数原封不动的传给另外一个函数。

语法

forward<T>(变量)

举例

void forward1(int && obj){
    cout << "obj为右值 "<<endl;
}

void forward1(int & obj){
    cout << "obj为左值 "<<endl;
}

void func7(){
    int a = 10;
    int &&b = 20;
    forward1(12);   //obj为右值
    forward1(a);    //obj为左值
    forward1(b);    //obj为左值
    forward1(forward<int>(b));//obj为右值
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/662802.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-18
下一篇 2022-04-18

发表评论

登录后才能评论

评论列表(0条)

保存