C++中的std::move、移动构造函数、右值、函数返回值联系

前言

本文简要概述std::move、移动构造函数、右值、函数返回值的概念及其相关联系。

一、std::move

在C++中std::move的作用很简单,一言以蔽之就是将左值变成右值。
常见用法:

int a = 10;
auto&& b = std::move(a);
class test{
public:
	int a = 0;
	int b = 0;
};
test c;
auto&& d = std::move(c);

二、左值、右值

左值:简单来说,就是普通的变量,正常使用的值。一言以蔽之,左值可以重复使用,并反复进行赋值拷贝。
右值:即将消亡的值,简单来说,右值就是即将消亡的值。一言以蔽之,右值只能使用一次。当使用右值引用即将消亡的右值时,该值就变成了左值,即可以被重复使用了。
引用:变量的别名,本质上就是不可为空的指针。

三、移动构造函数

移动构造函数简单来说就是接收一个右值的构造函数。
具体如下:

class test{
public:
	test(test&& tmp) {
		this->a = tmp;
		this->b = tmp;
	}
	int a = 0;
	int b = 0;
};

之所以叫移动构造函数,显然该构造函数与std::move有关。简单来说,就是使用将亡对象构造对象。
一般对于包含指针的对象来说,移动构造应当执行浅拷贝,即直接将指针值赋予对应的指针元素,并应当将被拷贝的指针设为空指针,避免二次释放内存。普通的拷贝构造函数执行深拷贝,重新申请内存,并将被拷贝的内存中的值赋值过来。

class test{
public:
	test(){ this->a = (int*)malloc(sizeof(int)*100); }
	test(test&& other){
		this->a = other.a;
		other.a = nullptr;
	}
	test(const test& other){
		this->a = (int*)malloc(sizeof(int)*100);
		memcpy(this->a, other.a, sizeof(int)*100);
	}
	~test(){
		if(a){
			free(a);
		}
	}
private:
	int *a;
};

三、函数返回值

1 debug模式

很显然函数返回值是一个将亡值。在接收函数返回值的时候会执行一次拷贝构造或者移动函数。但是C++编译器会非常智能,当存在拷贝构造函数的时候会自动执行拷贝构造函数,当存在移动构造函数的时候会默认先执行移动构造函数。值得注意的是,当存在接收目标的时候,编译器会将结果直接调用移动构造函数或者拷贝构造函数到目标位置,且不会创建临时变量。这与很多讲解C++函数调用返回的过程显然是不同的。

class Tet {
public:
	Tet(int id, int v1, int v2, int v3, int v4) {
		this->id = id;
		this->v1 = v1;
		this->v2 = v2;
		this->v3 = v3;
		this->v4 = v4;
	}
	Tet(const Tet& other) noexcept{
		this->id = other.id;
		this->v1 = other.v1;
		this->v2 = other.v2;
		this->v3 = other.v3;
		this->v4 = other.v4;
		std::cout << "copy constructor" << std::endl;
	}
	/* Tet(Tet&& tmp) noexcept {
		this->id = tmp.id;
		this->v1 = tmp.v1;
		this->v2 = tmp.v2;
		this->v3 = tmp.v3;
		this->v4 = tmp.v4;
		std::cout << "move constructor" << std::endl;
	} */
	int id;
	int v1;
	int v2;
	int v3;
	int v4;
};

Tet Test(int a) {
	Tet tet(1, 2, 3, 4, 5);
	tet.id += a;
	tet.v1 += a;
	tet.v2 += a;
	tet.v3 += a;
	tet.v4 += a;
	for (int i = 0; i < a; i++) {
		tet.id *= (i + 1) * 2;
		tet.v1 *= (i + 2);
		tet.v2 *= (i + 3);
		tet.v3 *= (i + 4);
		tet.v4 *= (i + 5);
	}
	for (int i = 1; i < a; i++) {
		tet.id += (i) * 3;
		tet.v1 += (i);
		tet.v2 += (i + 3);
		tet.v3 += (i + 4);
		tet.v4 += (i + 5);
	}
	return tet;
}

int main()
{
	int b;
	std::cin >> b;
	auto&& c = Test(b + 1);
	Tet ret = Test(b);
	std::cout << ret.id << " " << ret.v1 << " " << ret.v2 << " " << ret.v3 << " " << ret.v4 << std::endl;
	std::cout << c.id << " " << c.v1 << " " << c.v2 << " " << c.v3 << " " << c.v4 << std::endl;
	return 0;
}

执行拷贝构造函数
当取消掉移动构造函数注释以后:
执行移动构造函数

2 release模式

在release模式下会出现非常非常神奇的情况,强大的编译器将其变成了一种另类的构造函数,这真的是非常非常神奇。因为整个过程中完全没有出现调用拷贝构造或者移动构造的情况。当然,如果是更复杂的函数,可能情况会出现不同。
没有执行拷贝或者移动构造
以上结果完全是在Visual Studio 2022情况下产生的,在GCC中基本也属于相同模式。
以下是汇编代码,显然两者都采用了直接构造的技术:
GCC汇编
MSVC汇编

在这种模式下我们可以发现以下两种写法基本属于等价的:

#写法一
Tet Test(int a);
#写法二
void Test(Tet& output, int a);

总结

简单来说,std::move唯一的作用是产生右值,将左值变成右值。左值是可以多次使用的值,右值是即将消亡的值,只能使用一次,无论是左值引用还是右值引用,其代表的都是左值,可以被多次使用。右值可以被用来执行移动构造。对于函数调用,根据规范,必然会产生一次拷贝构造或者移动构造,一般会默认执行移动构造。而release模式下,可能会将其优化成一种另类的构造函数。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
std::move是C++11的一个标准库函数,用于将一个左值强制转换为右值引用。通过使用std::move,可以告诉编译器一个对象可以被移动而不是复制,从而提高程序的性能。std::move是一个类型转换函数,它不会真正移动数据,只是将左值转换成右值引用。 std::forward也是C++11的一个标准库函数,用于完美转发参数。当我们希望将一个函数的参数传递给另一个函数时,我们可以使用std::forward来保持参数的左右值属性。std::forward根据传入的参数类型来决定是将参数作为左值引用还是右值引用进行传递。 左右值引用是C++11引入的一个新的引用类型。左值引用指向一个具名的对象,而右值引用则可以绑定到一个临时对象或将要销毁的对象。左右值引用的一个重要应用是移动语义,通过将资源所有权从一个对象转移到另一个对象,避免了昂贵的资源拷贝操作。 移动构造函数是一种特殊的构造函数,用于在对象的移动操作进行资源移动而不是拷贝。在C++11,当一个对象被移动时,编译器会首先尝试调用其移动构造函数移动构造函数需要一个右值引用作为参数,并将其它对象的资源移动到当前对象,然后将原来的对象置为有效的但未知的状态。 综上所述,C++11std::move和std::forward以及左右值引用与移动构造函数都是为了实现移动语义而引入的新特性。它们可以提高程序的性能,避免不必要的资源拷贝,以及实现更高效的对象移动操作。但是在使用时需要注意正确的使用方式和避免潜在的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值