第十三章动态内存管理类

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

实现一个vector类的简化版本,不适用模板,只用于string,将其明明为StrVec。

将拷贝控制函数,析构函数中使用的函数定义在private,称为工具函数,方便使用。

StrVec类设计

模拟vector类将元素保存在连续内存

  • vector预先分配足够的内存来保存可能需要的更多的元素
  • vector的每个添加元素的成员函数都会检查是够有空间可以容纳更多元素。如果有,成员函数在下一个可用位置构造一个对象;否则vector重新分配空间:它获得新的空间,将已有元素移动到新空间,释放旧空间,并且添加新元素。

 我们使用allocator(allocator详解)获取原始内存;添加新元素必须使用construct成员创建对象;删除元素必须使用destroy成员来销毁。

每个StrVec有三个指针成员指向其元素所使用的内存:

  • elements,指向分配内存的首元素
  • first_free,指向最后一个实际元素之后的位置
  • cap,指向分配的内存末尾之后的位置

 

StrVec还有一个alloc的静态成员,其类型为allocator<string>。alloc成员会分配StrVec使用的内存。类还有4个工具函数:

  • alloc_n_copy会分配内存,并拷贝一个给定范围中的元素
  • free会销毁构造的元素并释放内存
  • chk_n_alloc保证StrVec至少有容纳一个新元素的空间;否则会调用reallocate分配更多内存
  • reallocate在内存用完时为StrVec分配新内存

定义vector接口中的一些成员

StrVec类定义

//实现一个简易vector模板类
#include<iostream>
#include<string>
#include<allocators>
using namespace std;
class StrVec {
public:
	StrVec():elements(NULL), first_free(NULL),cap(NULL){}
	StrVec(const StrVec&);
	StrVec& operator=(const StrVec&);
	~StrVec();
	void push_back(const string&);

	size_t size() const { return first_free - elements; }
	size_t capacity() const { return cap - elements; }

	string* begin() const { return elements; }
	string* end()	const { return first_free; }
private:
	static allocator<string> alloc;   //why static
	void chk_n_alloc() { if (size() == capacity()) rellocate(); }
	pair<string*, string*> alloca_n_copy(string*, string*);
	void free();
	void rellocate();

	string* elements;//数组首元素指针
	string* first_free;//数组中第一个空闲位置
	string* cap;//数组尾后位置指针

};

使用construct

函数push_back调用chk_n_alloc确保有空间容纳新元素。如果需要,调用reallocate。当chk_n_alloc返回时,必有空间容纳新元素,要求allocator成员construct新的尾元素:

传递给construct的第一参数必须是一个指针,剩余的参数确定用哪个构造函数来构造对象。

void StrVec::push_back(const string& s) {
	chk_n_alloc();
	alloc.construct(first_free++, s);
}

值得注意的是,construct的调用也会递增first_free,表示已经够早了一个新元素,采用前置递增,因此在这个调用会在first_free当前值指定的位置构造一个对象,并递增first_free指向下一个未构造的元素。

【不能理解,construct调用会前置递增first_free?first_free不是已经指向第一个空闲空间吗?再递增干嘛?而且first_free之后又递增一次?】

alloc_n_copy成员

类似于vector,StrVec类有类值的行为。当我们拷贝或者赋值StrVec时,必须分配独立的内存,并且从原StrVec对象拷贝元素至新对象。

alloc_n_copy返回一个指针的pair对,分别指向新空间的开始位置最后一个构造元素后的位置

pair<string*, string*> StrVec::alloca_n_copy(const string* b, const string* e) {
	auto data = alloc.allocate(e - b);
	return{ data,uninitialized_copy(b,e,data); }
}

这段代码有两点要注意:

  • data是新开辟的一块内存,类型和alloc有关
  • uninitialized_copy(b,e,data) 表示拷贝指针b,e之间的元素到新开辟的内存p之中,并且该函数返回值是新开辟的空间的最后一个构造元素之后的位置

free成员

free成员两个责任:首先destroy元素,然后释放StrVec自己开辟的空间。

void StrVec::free() {
	//不能传递给deallocate一个空指针
	if (elements) {
		for (auto p = first_free; p != elements;)
			alloc.destroy(--p);//p指向最后一个元素之后的位置,首先要让它指回最后一个元素
		alloc.deallocate(elements, cap - elements);
	}
}

注意:传递给deallocate的指针必须是之前某次allocate调用所返回的指针,在调用deallocate之前我们首先得检查elements是否为空。

拷贝控制成员

StrVec::StrVec(const StrVec&s) {
	auto newdata = alloca_n_copy(s.begin(), s.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}

析构函数

StrVec::~StrVec() {
	free();
}

拷贝赋值函数

注意:自赋值的问题,在释放这块内存之前先进行拷贝。

StrVec& StrVec::operator=(const StrVec& rhs) {
	auto data = alloca_n_copy(rhs.begin(), rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

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

StrVec重新分配内存会由旧空间到新空间逐个拷贝string,string具有类值行为。当拷贝一个string,新的string和原string之间是相互独立的,改变stirng不会影响到副本,并且一旦将元素从旧空间拷贝到新空间,我们又立马销毁原strring。所以如果我们能避免分配和释放string的额外开销,StrVec性能会好的多。

移动构造函数和std::move

通过使用新标准库映入的两种机制,可以避免string的拷贝。首先,一些标准库类,包括string都定义了所谓的“移动构造函数”。移动构造函数是将资源从给定对象“移动”而不是拷贝到正在创建的对象。对于string,可以想象每个string都有一个指向char数组的指针,可以假定sstring的移动构造函数进行了指针的拷贝,而不是为字符分配内存空间然后再拷贝字符。

我们使用的第二个机制是名为move的标准库函数,定义在utility头文件中。关于move需要了解两个关键点,首先,在新内存构造string时,必须调用move表示希望使用string的移动构造函数;其次,我们通常不为move提供一个using声明而直接使用std::move。

rellocate成员

void StrVec::rellocate() {
	auto newcapacity = size() ? 2 * size() : 1;
	auto newdata = alloc.allocate(newcapacity);
	auto dest = newdata;//指向新数组下一个空闲位置,这里是新开辟的内存,所以当前是首位置
	auto elem = elements;//指向旧数组下一个元素的位置
	for (size_t i = 0; i != size(); ++i) {
		alloc.construct(dest++, std::move(*elem++));
	}
	free();//释放旧空间
	elements = newdata;
	first_free = dest;
	cap = elements + newcapacity;
}

整体代码:

#include<iostream>
#include<string>
//#include<allocators>
#include<memory>
using namespace std;
class StrVec {
public:
	StrVec():elements(NULL), first_free(NULL),cap(NULL){}
	StrVec(const StrVec&);
	StrVec& operator=(const StrVec&);
	~StrVec();
	void push_back(const string&);

	size_t size() const { return first_free - elements; }
	size_t capacity() const { return cap - elements; }

	string* begin() const { return elements; }
	string* end()	const { return first_free; }
private:
	static allocator<string> alloc;   //why static
	void chk_n_alloc() { if (size() == capacity()) rellocate(); }
	pair<string*, string*> alloca_n_copy(const string*, const string*);
	void free();
	void rellocate();

	string* elements;//数组首元素指针
	string* first_free;//数组中第一个空闲位置
	string* cap;//数组尾后位置指针
};

void StrVec::push_back(const string& s) {
	chk_n_alloc();
	alloc.construct(first_free++, s);
}
pair<string*, string*> StrVec::alloca_n_copy(const string* b, const string* e) {
	auto data = alloc.allocate(e - b);
	return{ data,uninitialized_copy(b,e,data) };
}

void StrVec::free() {
	//不能传递给deallocate一个空指针
	if (elements) {
		for (auto p = first_free; p != elements;)
			alloc.destroy(--p);//p指向最后一个元素之后的位置,首先要让它指回最后一个元素
		alloc.deallocate(elements, cap - elements);
	}
}
StrVec::StrVec(const StrVec&s) {
	auto newdata = alloca_n_copy(s.begin(), s.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}
StrVec::~StrVec() {
	free();
}
StrVec& StrVec::operator=(const StrVec& rhs) {
	auto data = alloca_n_copy(rhs.begin(), rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}
void StrVec::rellocate() {
	auto newcapacity = size() ? 2 * size() : 1;
	auto newdata = alloc.allocate(newcapacity);
	auto dest = newdata;//指向新数组下一个空闲位置,这里是新开辟的内存,所以当前是首位置
	auto elem = elements;//指向旧数组下一个元素的位置
	for (size_t i = 0; i != size(); ++i) {
		alloc.construct(dest++, std::move(*elem++));
	}
	free();//释放旧空间
	elements = newdata;
	first_free = dest;
	cap = elements + newcapacity;
}

猜你喜欢

转载自blog.csdn.net/qq_34269988/article/details/88761078