C++內(nèi)存篇(四):寫一個類似vector的簡單動態(tài)內(nèi)存管理類

動態(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)存分配方式:

  1. 預(yù)先分配足夠的內(nèi)存,將元素保存在連續(xù)的內(nèi)存中
  2. 添加元素的成員函數(shù)檢查是否有空間容納更多的元素
  3. 如果有,在下一個可用位置構(gòu)造新對象
  4. 如果沒有空間了,vector將重新分配空間:獲得一塊新的更大空間,將原有元素放入新空間,并釋放舊空間。

設(shè)計自己的類

我們在內(nèi)存分配上采取和vector類似的策略。

我們來看看我們需要什么類成員來完成一個類似vector的行為:

公有成員

1、關(guān)于構(gòu)造、析構(gòu)和拷貝賦值的

  1. 默認(rèn)構(gòu)造函數(shù):

    StrVec()
    
  2. 三個拷貝控制成員:拷貝構(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)存控制的

  1. 內(nèi)存分配器:

    因為直到主程序結(jié)束前我們可能一直需要內(nèi)存分配器來完成一些功能,所以它需要是一個靜態(tài)變量。

    (靜態(tài)變量一旦聲明就會直到主程序結(jié)束才被銷毀)

    static std::allocator<std::string> alloc //用來給該類分配一塊string類型的內(nèi)存
    
  2. 一個檢查是否還有剩余空間、如果沒有就重新分配內(nèi)存的函數(shù)

    void chk_n_alloc()
    
  3. 用于重新分配內(nèi)存的函數(shù)

    void reallocate()
    
  4. 分配空間并接受參數(shù)拷貝其他內(nèi)存上的對象的函數(shù):

    pair<string*, string*> alloc_n_copy(const string*, const string*)
    

    alloc_n_copy接受兩個string指針,分別是要拷貝的string的內(nèi)存塊首地址和尾地址,返回值是一個pair,包含為新分配的內(nèi)存首地址和末地址。

  5. 銷毀元素并釋放內(nèi)存的函數(shù):

    free()
    
  6. 重新分配內(nèi)存:chk_n_alloc()檢測到空間不夠時調(diào)用它,獲得一塊更大的內(nèi)存,并把原來的對象移動到上面。我們使用move來完成移動而不是拷貝,如果使用拷貝我們需要把舊的對象拷貝到新的更大的內(nèi)存上,再釋放原有的內(nèi)存,這樣浪費了一遍構(gòu)造對象過程,而如果使用移動,我們會讓原有內(nèi)存上的對象直接去管理新的內(nèi)存而不用再構(gòu)造它們,然后同樣會釋放原有內(nèi)存。

  7. 一些標(biāo)記內(nèi)存位置的成員:

string* elements;   //指向被分配的內(nèi)存的首地址
string* first_free; //指向數(shù)組第一個空閑元素的指針
string* cap;        //指向被分配的內(nèi)存的末地址后一位
image-20200427222101809.png

代碼

#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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容