C++11——移动构造函数及std::move() 的使用

复制构造和移动构造

复制构造是这样的:
在对象被复制后临时对象和复制构造的对象各自占有不同的同样大小的堆内存,就是一个副本。
在这里插入图片描述
移动构造是这样的:
就是让这个临时对象它原本控制的内存的空间转移给构造出来的对象,这样就相当于把它移动过去了。
在这里插入图片描述
复制构造和移动构造的差别:
这种情况下,我们觉得这个临时对象完成了复制构造后,就不需要它了,我们就没有必要去首先产生一个副本,然后析构这个临时对象,这样费两遍事,又占用内存空间,干脆将临时对象原本的堆内存直接转给构造的对象就行了。 当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制构造。

什么时候该触发移动构造呢?
如果临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候我们就可以触发移动构造。

std::move

std::move函数可以以非常简单的方式将左值转换为右值引用
通过std::move,可以避免不必要的拷贝操作。
std::move是为性能而生。

int main()
{
	string str = "Hello";//这里假设我们只需要将str的内容放到vector中,完成以后永远都不需要再用到str
	vector<string> v;
	//调用常规的拷贝构造函数,新建字符数组,拷贝数据
	v.push_back(str);
	cout << "After copy, str is :" << str << endl;
	//先把str转为右值引用,然后调用移动构造函数转交所有权
	v.push_back(move(str));
	cout << "After move, str is:" << str << endl;
	cout << "The contents of the vector are:{" << v[0]
		<< "," << v[1] << "}"<<endl;

	system("pause");
	return 0;
}

string类的数据成员简单的来说其实最关键的就是一个char*(至于其他的东西不重要),指向动态内存分配空间的首地址。这里可以看我的文章:string类的简单实现

在这里插入图片描述

再看一个例子:

int main()
{
	string s1 = "apple";
	string s2 = "banana";

	s1 = move(s2); 
	// s1=="banana"
	cout << s2 << endl;

	vector<int> v1;
	vector<int> v2 = { 1, 2, 3, 4 };

	v1 = move(v2);  //从v2转移到v1
	//v1=={1, 2, 3, 4}
	system("pause");
	return 0;

}

s1 = move(s2);move函数先把s2转为右值引用,然后就可以执行string类的移动构造函数和移动赋值运算符函数。

继续深入分析C++11的右值引用:

1.什么是左值,什么是右值,简单说左值可以赋值,右值不可以赋值。以下面代码为例,“ A a = getA();”该语句中a是左值,getA()的返回值是右值。

class A
{
public:
	A() { cout << "Constructor" << endl; }
	A(const A&) { cout << "Copy Constructor" << endl; }
	~A() {}
};

A getA()
{
	A a;
	return a;
}

int main()
{
	A a = getA();// getA()是一个右值
	system("pause");
	return 0;
}

在这里插入图片描述
可以看到A的构造函数调用一次,拷贝构造函数调用了一次,构造函数和拷贝构造函数是消耗比较大的,这里是否可以避免拷贝构造?C++11做到了这一点。(注意,上面这个例子中,虽然getA()是一个右值,但是由于没有自定义move constructor,所以调用了默认的copy constructor。如果对象中有堆内存管理,必须定义move constructor。)

2.自定义A的移动构造函数,代码如下:

class A
{
public:
	A() { cout << "Constructor" << endl; }
	A(const A&) { cout << "Copy Constructor" << endl; }
	A(const A&&) { cout << "Move Constructor" << endl; }
	~A() {}
};

A getA()
{
	A a;
	return a;
}

int main()
{
	A a = getA();// getA()是一个右值
	system("pause");
	return 0;
}

在这里插入图片描述
这样就没有调用拷贝构造函数,而是调用移动构造。这里并没有看到移动构造的优点。

3.修改代码,给A类添加一个成员变量如下:

class B
{
public:
	B() {}
	B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
	A() : pb(new B()) { cout << "A Constructor" << endl; }
	A(const A& src) :pb(new B(*(src.pb)))//深拷贝
	{
		cout << "A Copy Constructor" << endl;
	}
	A(A&& src) :pb(src.pb)
	{
		src.pb = nullptr;//这里是关键,这样以后,当src.pb被delete时,由于其为空指针,并不会释放原来的堆内存
		cout << "A Move Constructor" << endl;
	}
	~A() { delete pb; }

private:
	B* pb;
};

static A getA()
{
	A a;
	cout << "================================================" << endl;
	return a;
}

int main()
{
	A a = getA();
	cout << "================================================" << endl;
	A a1(a);
	system("pause");
	return 0;
}

在这里插入图片描述
A a = getA();调用的是A的移动构造,A a1(a); 调用的是A的拷贝构造。A的拷贝构造需要对成员变量B进行深拷贝,而A的移动构造不需要,很明显,A的移动构造效率高。

4.std::move语句可以将左值变为右值而避免拷贝构造,修改代码如下:

class B
{
public:
	B() {}
	B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
	A() : pb(new B()) { cout << "A Constructor" << endl; }
	A(const A& src) :pb(new B(*(src.pb)))//深拷贝
	{
		cout << "A Copy Constructor" << endl;
	}
	A(A&& src) :pb(src.pb)
	{
		src.pb = nullptr;
		cout << "A Move Constructor" << endl;
	}
	~A() { delete pb; }

private:
	B* pb;
};

static A getA()
{
	A a;
	cout << "================================================" << endl;
	return a;
}

int main()
{
	A a = getA();
	cout << "================================================" << endl;
	A a1(a);
	cout << "================================================" << endl;
	A a2(move(a));
	system("pause");
	return 0;
}

在这里插入图片描述
A a2(std::move(a));将a转换为右值,因此a2调用的是移动构造而不是拷贝构造。

5.赋值操作符也可以是移动赋值。

class B
{
public:
	B() {}
	B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
	A() : pb(new B()) { cout << "A Constructor" << endl; }
	A(const A& src) :pb(new B(*(src.pb)))//深拷贝
	{
		cout << "A Copy Constructor" << endl;
	}
	A(A&& src) :pb(src.pb)
	{
		src.pb = nullptr;
		cout << "A Move Constructor" << endl;
	}


	A& operator=(const A& src) noexcept//深拷贝
	{
		if (this == &src)
			return *this;

		delete pb;
		pb = new B(*(src.pb));
		cout << "operator=(const A& src)" << endl;
		return *this;
	}
	A& operator=(A&& src) noexcept
	{
		if (this == &src)
			return *this;

		delete pb;
		pb = src.pb;
		src.pb = nullptr;
		cout << "operator=(const A&& src)" << endl;
		return *this;
	}
	~A() { delete pb; }

private:
	B* pb;
};

static A getA()
{
	A a;
	cout << "================================================" << endl;
	return a;
}

int main()
{
	A a = getA();//移动构造
	cout << "================================================" << endl;
	A a1(a);//拷贝构造
	cout << "================================================" << endl;
	A a2(move(a));//移动构造
	cout << "================================================" << endl;
	a2 = getA();//移动赋值(因为getA()是右值)
	cout << "================================================" << endl;
	a2 = a1;//拷贝赋值(因为a1是左值)
	system("pause");
	return 0;
}

在这里插入图片描述
总之尽量给类添加移动构造和移动赋值函数,而减少拷贝构造和拷贝赋值的消耗。 移动构造,移动赋值要加上noexcept,用于通知标准库不抛出异常。

使用move函数转交unique_ptr的所有权

#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char *argv[])
{
	
	int*p1 = new int(56);
	unique_ptr<int> up_int1(p1);
	unique_ptr<int> up_int2= move(up_int1);//转交所有权(法1)
	//unique_ptr<int> p2(p1.release()); //转交所有权(法2)
	

	string* p2 = new string[2];//对象数组
	p2[0] = "apple";
	p2[1] = "banana";
	unique_ptr<string[]> up_str1(p2);
	unique_ptr<string[]> up_str2;
	up_str2 = move(up_str1);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值