【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;
}

浙公网安备 33010602011771号