【C++ primer阅读记录】内存管理之swap交换函数与std::move

Swap交换

有管理到内存资源的类通常会定义一个名为swap的函数,对于需要使用进行排序算法的类swap是特别重要的,它能够交换两个元素。

swap操作通常包括一次拷贝初始化(设置临时中间量)和两次赋值操作。

HasPtr temp = v1;
v1 = v2;
v2 = temp;

这需要分配一个新的string,但是是不必要的。比起分配一份string的拷贝,我们能乐于交换指针。

string *temp = v1.ps;
v1.ps = v2.ps;
v2.ps = temp;
inline void swap(HasPtr &lhs, HasPtr &rhs)
{
	using std::swap;
	swap(lhs.ps,rhs.ps); //交换指针,而不是string的data
	swap(lhs.i,rhs.i); 
}

需要把这个函数设置为Hasptr类的友元函数来访问HasPtr的私人数据成员。

重载swap

我们可以定义一个自己版本的swap来重载swap的默认行为。

每个swap调用都应该是未加限定的,也就是都应该用swap而不是std::swap。如果存在特定类型的swap版本,其匹配程度都会由于swap版本。

void swap(Foo& lhs, Foo& rhs)
{
	using std::swap; //但是这里为什么没有隐藏掉HasPtr版本的swap?
	swap(lhs.h,rhs,h); //使用的是HasPtr版本的swap
}

在赋值运算符中使用swap

HasPtr& HasPtr::operator=(HasPtr rhs) //rhs不是引用,是右值运算对象的新副本
{
	swap(*this,rhs); //rhs获得了原来旧值的内存空间
	return *this;
}//函数结束后会释放掉rhs。

这样子解决了自赋值的问题。

动态内存管理类

某些类需要在运行时分配可变大小的内存空间,通常可以使用标准库容器来保存它们的数据。但是某些类需要自己进行内存分配的话,需要定义自己的拷贝控制成员来管理分配的内存。

StrVec类的设计

vector类将其元素保存在连续内存中,并且预先分配足够的内存来保存可能需要的更多元素。如果有足够的空间,成员函数就会在下一个可用位置构造一个对象。如果没有可用空间,vector就会重新分配空间,把旧元素移动到新空间中,释放旧空间,添加新元素。

其实就是使用allocator来获得原始内存,添加新元素的时候用allocator的construct成员在原始内存中创建对象。需要删除一个元素时,用destroy销毁元素。

在重新分配内存的过程中移动而不是拷贝元素

在编写reallocate成员函数之前,可以想到它需要实现:

·为一个新的、更大的string数组分配内存

·在新的内存空间的前一部分构造对象,保存现有元素

·销毁原内存空间中的元素,并释放这块内存

可以看到这会引起从旧内存空间到新内存空间逐个拷贝string。拷贝这些string中的数据是多余的,在重新分配内存空间时,如果能避免分配和释放string的额外开销,StrVec的性能就会好得多。

移动构造函数和std::move

通过使用新标准库引入的两种机制,我们可以避免string的拷贝。关于string的移动构造函数如何工作的细节以及有关实现的任何细节,尚未公开。

move的标准库函数,定义在utility头文件中。当reallocate在新内存构造string时,调用move来表示希望用string的移动构造函数。移动构造函数是将资源从给定对象“移动”而不是拷贝到正在创建的对象。而且我们知道标准库保证moved-from string仍然保持一个有效的,可析构的状态。

void StrVec::reallocate()
{
	auto newcapacity = size()?2*size():1;
	auto newdata = alloc.allocate(newcapcity); //通过allocator分配的新空间,并且返回这部分空间的     //首元素地址给了newdata,实际上在直接管理这部分空间的目前只有newdata。
	
	auto dest = newdata; //指向新数组的下一个空间位置 
	auto elem = elements;//是旧数组内存空间的下一个元素
	for(size_t i =0;i!=size();++i)
	{
		alloc.construct(dest++,std::move(*elem++));
		//construct的第二个参数确定使用哪个构造函数。但是在这里是move的返回值。
		//调用move返回的结果会让construct使用string的移动构造函数。这个string指的是新内存空间的未构造		//的string。
		//在移动构造函数中,string管理的内存将不会被拷贝。我们构造的每个string都会从elem指向的
		//老内存空间中的string接管内存的所有权。
	}
	free(); //元素移动完毕后,调用free销毁旧元素。
	elements = newdata;
	first_free = dest;
	cap = elements + newcapacity;
}
posted @ 2020-12-04 22:06  LeeSCUT  阅读(845)  评论(0)    收藏  举报