動態(tài)內(nèi)存管理類
某些類在運行時分配可變大小的內(nèi)存空間,這種類如果可以的話應(yīng)該使用標(biāo)準(zhǔn)容器庫來保存它的數(shù)據(jù)。例如用一個vector來管理其底層內(nèi)存。
但是有些類卻需要自己進(jìn)行內(nèi)存分配,這樣就必須定義自己的拷貝控制成員來管理內(nèi)存分配??截惪刂瞥蓡T包括:
- 拷貝構(gòu)造函數(shù)
- 拷貝賦值賦值運算符(即=運算符)
- 析構(gòu)函數(shù)
下面來實現(xiàn)vector的一個(極度)簡化版本,該版本只針對string元素,故將它命名為StrVec
vector的內(nèi)存分配方法
讓我們先回顧一下vector的內(nèi)存分配方式:
- 預(yù)先分配足夠的內(nèi)存,將元素保存在連續(xù)的內(nèi)存中
- 添加元素的成員函數(shù)檢查是否有空間容納更多的元素
- 如果有,在下一個可用位置構(gòu)造新對象
- 如果沒有空間了,vector將重新分配空間:獲得一塊新的更大空間,將原有元素放入新空間,并釋放舊空間。
設(shè)計自己的類
我們在內(nèi)存分配上采取和vector類似的策略。
我們來看看我們需要什么類成員來完成一個類似vector的行為:
公有成員
1、關(guān)于構(gòu)造、析構(gòu)和拷貝賦值的
-
默認(rèn)構(gòu)造函數(shù):
StrVec() -
三個拷貝控制成員:拷貝構(gòu)造函數(shù)、拷貝賦值運算符、析構(gòu)函數(shù)
StrVec(const StrVec&) //拷貝構(gòu)造函數(shù) StrVec& operator=(const StrVec&) //拷貝賦值運算符 ~StrVec() //析構(gòu)函數(shù)
2、其他功能
void push_back(const string&) //拷貝元素
size_t size() const //返回當(dāng)前存儲著多少個元素
size_t capacity() const //返回當(dāng)前這個類擁有的內(nèi)存可以放多少個元素
string* begin() const //返回當(dāng)前用來存放元素的內(nèi)存塊的首地址
string* end() const //返回當(dāng)前用來存放元素的內(nèi)存塊的第一個空閑地址
私有成員
1.關(guān)于內(nèi)存控制的
-
內(nèi)存分配器:
因為直到主程序結(jié)束前我們可能一直需要內(nèi)存分配器來完成一些功能,所以它需要是一個靜態(tài)變量。
(靜態(tài)變量一旦聲明就會直到主程序結(jié)束才被銷毀)
static std::allocator<std::string> alloc //用來給該類分配一塊string類型的內(nèi)存 -
一個檢查是否還有剩余空間、如果沒有就重新分配內(nèi)存的函數(shù)
void chk_n_alloc() -
用于重新分配內(nèi)存的函數(shù)
void reallocate() -
分配空間并接受參數(shù)拷貝其他內(nèi)存上的對象的函數(shù):
pair<string*, string*> alloc_n_copy(const string*, const string*)alloc_n_copy接受兩個string指針,分別是要拷貝的string的內(nèi)存塊首地址和尾地址,返回值是一個pair,包含為新分配的內(nèi)存首地址和末地址。
-
銷毀元素并釋放內(nèi)存的函數(shù):
free() 重新分配內(nèi)存:
chk_n_alloc()檢測到空間不夠時調(diào)用它,獲得一塊更大的內(nèi)存,并把原來的對象移動到上面。我們使用move來完成移動而不是拷貝,如果使用拷貝我們需要把舊的對象拷貝到新的更大的內(nèi)存上,再釋放原有的內(nèi)存,這樣浪費了一遍構(gòu)造對象過程,而如果使用移動,我們會讓原有內(nèi)存上的對象直接去管理新的內(nèi)存而不用再構(gòu)造它們,然后同樣會釋放原有內(nèi)存。一些標(biāo)記內(nèi)存位置的成員:
string* elements; //指向被分配的內(nèi)存的首地址
string* first_free; //指向數(shù)組第一個空閑元素的指針
string* cap; //指向被分配的內(nèi)存的末地址后一位

代碼
#include<iostream>
#include<utility>
using namespace std;
//動態(tài)內(nèi)存管理類
class StrVec {
public:
//___________________關(guān)于構(gòu)造、析構(gòu)和拷貝賦值的________________________
StrVec() : //allocator成員默認(rèn)初始化
elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const StrVec&); //拷貝構(gòu)造函數(shù)
StrVec& operator=(const StrVec&); //拷貝賦值運算符
~StrVec(); //析構(gòu)函數(shù)
//___________________其他功能__________________________________________
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 std::allocator<std::string> alloc; //分配元素
//被添加元素的函數(shù)所使用
void chk_n_alloc() {
if (size() == capacity()) reallocate();
}
//工具函數(shù),被拷貝構(gòu)造函數(shù)、賦值運算符和析構(gòu)函數(shù)所使用
pair<string*, string*> alloc_n_copy(const string*, const string*);
void free(); //銷毀元素并釋放內(nèi)存
void reallocate(); //獲得更多內(nèi)存并拷貝已有元素
string* elements; //指向數(shù)組首元素的指針
string* first_free; //指向數(shù)組第一個空閑元素的指針
string* cap; //指向數(shù)組尾后位置的指針
};
//___________________________內(nèi)存控制___________________________________
//alloc_n_copy:
//參數(shù)為拷貝對象的首地址和末地址
//返回值為新分配的內(nèi)存首地址和末地址
pair<string*,string*>
StrVec::alloc_n_copy(const string* b, const string* e) {
//分配空間保存給定范圍中的元素
auto data = alloc.allocate(e - b);
//初始化并返回一個pair
//該pair由data和uninitialized_copy的返回值
//(該返回值是一個指針),指向最后一個構(gòu)造元素的位置
//uninitialized_copy以指針data為首地址,把內(nèi)存地址b到內(nèi)存地址e上的對象拷貝到data這里
return { data, uninitialized_copy(b,e,data) };
}
void StrVec::push_back(const string& s){
chk_n_alloc(); //確保有空間容納新元素
//再first_free指向的元素中構(gòu)造s的副本
alloc.construct(first_free++, s);
}
void StrVec::free() {
//不能傳遞給deallocate一個空指針,如果elements為0,函數(shù)什么也不做
if (elements) {
//逆序銷毀元素
for (auto p = first_free; p != elements;)
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
//___________________________拷貝控制成員_______________________________
//拷貝構(gòu)造函數(shù)
StrVec::StrVec(const StrVec& s) {
//調(diào)用alloc_n_copy分配空間以容納與s中一樣多的元素
auto newdata = alloc_n_copy(s.begin(),s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
//析構(gòu)函數(shù):調(diào)用free
StrVec::~StrVec() { free(); }
//拷貝賦值運算符
StrVec &StrVec::operator=(const StrVec &rhs){
//調(diào)用alloc_n_copy分配內(nèi)存,大小與rhs中元素占用空間一樣多
auto data = alloc_n_copy(rhs.begin(), rhs.end());
elements = data.first;
first_free = cap = data.second;
return *this;
}
void StrVec::reallocate() {
//分配當(dāng)前大小兩倍的內(nèi)存空間
//如果原本沒有空間,則分配容納一個元素的空間
auto newcapacity = size() ? 2 * size() : 1;
//調(diào)用分配器拿到一塊新內(nèi)存
auto newdata = alloc.allocate(newcapacity);
//將數(shù)據(jù)從舊內(nèi)存移動到新內(nèi)存
auto dest = newdata; //新內(nèi)存的首地址
auto elem = elements; //舊內(nèi)存的首地址
for (size_t i = 0; i != size(); ++i)
//調(diào)用move會使construct使用string的移動構(gòu)造函數(shù)而不是拷貝構(gòu)造函數(shù)
alloc.construct(dest++,std::move(*elem++));
//移動完元素就釋放舊空間
free();
//更新數(shù)據(jù)結(jié)構(gòu),執(zhí)行新單元
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}