C++中allocator類使用示例

動態內存管理

之前我們講述過動態內存的開辟,可以通過new, malloc,以及alloc等方式,本文通過介紹alloc方式,構造一個StrVec類,這個類的功能類似於一個vector,實現字符串的管理,其中包含push一個字符串,動態擴容,析構,回收內存等操作。

StrVec類實現細節

StrVec類實現如下

class StrVec
{
public:
    //無參構造函數
    StrVec() : elements(nullptr), first_free(nullptr),
               cap(nullptr) {}
    //拷貝構造函數
    StrVec(const StrVec &);
    //拷貝賦值運算符
    StrVec &operator=(const StrVec &);
    //析構函數
    ~StrVec();
    //拷貝元素
    void push_back(const std::string &);
    //返回元素個數
    size_t size() const { return first_free - elements; }
    //返回總容量
    size_t capacity() const { return cap - elements; }
    //返回首元素地址
    std::string *begin() const
    {
        return elements;
    }
    //返回第一個空閑元素地址
    //也是最後一個有效元素的下一個位置
    std::string *end() const
    {
        return first_free;
    }

private:
    //判斷容量不足開辟新空間
    void chk_n_alloc()
    {
        if (size() == capacity())
            reallocate();
    }
    //重新開辟空間
    void reallocate();
    // copy指定范圍的元素到新的內存中
    std::pair<std::string *, std::string *> alloc_n_copy(
        const std::string *, const std::string *);
    //釋放空間
    void free();
    //數組首元素的指針
    std::string *elements;
    //指向數組第一個空閑元素的指針
    std::string *first_free;
    //指向數組尾後位置的指針
    std::string *cap;
    //構造string類型allocator靜態成員
    static std::allocator<std::string> alloc;
};

1 elements成員,該成員指向StrVec內部數組空間的第一個元素
2 first_free成員指向第一個空閑元素,也就是有效元素的下一個元素,該元素開辟空間但未構造。
3 cap 指向最後一個元素的下一個位置。
4 alloc為靜態成員,主要負責string類型數組的開辟工作。
5 無參構造函數將三個指針初始化為空,並且默認夠早瞭alloc。
6 alloc_n_copy私有函數的功能是將一段區域的數據copy到新的空間,
並且返回新開辟的空間地址以及第一個空閑元素的地址(第一個未構造元素的地址)。
7 chk_n_alloc私有函數檢測數組大小是否達到容量,如果達到則調用reallocate重新開辟空間。
8 reallocate重新開辟空間
9 capacity返回總容量
10 size返回元素個數
11 push_back 將元素放入開辟的類似於數組的連續空間中。
12 begin返回首元素地址
13 end返回第一個空閑元素地址,也是最後一個有效元素的下一個位置
無論我們實現push操作還是拷貝構造操作,都要實現realloc,當空間不足時要開辟空間將舊數據移動到新的數據

//重新開辟空間
void StrVec::reallocate()
{
    string *newdata = nullptr;
    //數組為空的情況
    if (elements == nullptr || cap == nullptr || first_free == nullptr)
    {
        newdata = alloc.allocate(1);
        // elements和first_free都指向首元素
        elements = newdata;
        first_free = newdata;
        // cap指向數組尾元素的下一個位置。
        cap = newdata + 1;
        return;
    }
    //不為空則擴充兩倍空間
    newdata = alloc.allocate(size() * 2);
    //新內存空閑位置
    auto dest = newdata;
    //舊內存有效位置
    auto src = elements;
    //通過移動操作將舊數據放到新內存中
    for (size_t i = 0; i != size(); ++i)
    {
        alloc.construct(dest++, std::move(*src++));
    }
    //移動後舊內存數據無效,一定要刪除
    free();
    //更新數據位置
    elements = newdata;
    //更新第一個空閑位置
    first_free = dest;
    //更新容量
    cap = elements + size() * 2;
}

reallocate函數內部判斷是否為剛初始化指針卻沒開辟空間的空數組,如果是則開辟1個大小的空間。
否則則開辟原有空間的兩倍,將舊數據移動到新空間,采用瞭std::move操作,這麼做減少拷貝造成的性能開銷。
move之後原數據就無效瞭,所以要調用私有函數free()進行釋放。我們實現該free操作

//釋放操作
void StrVec::free()
{
    //判斷elements是否為空
    if (elements == nullptr)
    {
        return;
    }

    auto dest = elements;
    //要先遍歷析構每一個對象
    for (size_t i = 0; i < size(); i++)
    {
        // destroy會調用每一個元素的析構函數
        alloc.destroy(dest++);
    }
    //再整體回收內存
    alloc.deallocate(elements, cap - elements);
}

先通過遍歷destroy銷毀內存,從而調用string的析構函數,最後在deallocate回收內存。

// copy指定范圍的元素到新的內存中,返回新元素的地址和第一個空閑元素地址的pair
std::pair<std::string *, std::string *> StrVec::alloc_n_copy(
    const std::string *b, const std::string *e)
{
    auto newdata = alloc.allocate(e - b);
    //將原數據用來初始化新空間
    auto first_free = uninitialized_copy(b, e, newdata);
    return {newdata, first_free};
}

這樣利用alloc_n_copy,我們就可以實現拷貝構造和拷貝賦值瞭

//拷貝構造函數
StrVec::StrVec(const StrVec &strtmp)
{
    //將形參數據拷貝給自己
    auto rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, cap,first_free
    elements = rsp.first;
    first_free = rsp.second;
    cap = rsp.second;
}

但是拷貝賦值要註意一點,就是自賦值的情況,所以我們提前判斷是否為自賦值,如不是則進行和拷貝構造相同的操作

//拷貝賦值運算符
StrVec &StrVec::operator=(const StrVec &strtmp)
{
    //防止自賦值
    if (this == &strtmp)
    {
        return *this;
    }
    //將形參數據拷貝給自己
    auto rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, cap,first_free
    elements = rsp.first;
    first_free = rsp.second;
    cap = rsp.second;
}

我們可以利用free實現析構函數

//析構
StrVec::~StrVec()
{
    free();
}

接下來我們實現push_back,將指定字符串添加到數組空間,以及拋出元素

//添加元素
void StrVec::push_back(const std::string &s)
{
    chk_n_alloc();
    alloc.construct(first_free++, s);
}

//拋出元素
void StrVec::pop_back(std::string &s)
{
    if (first_free == nullptr)
    {
        return;
    }

    if (size() == 1)
    {
        
        s = *elements;
        alloc.destroy(elements);
        first_free = nullptr;
        elements = nullptr;
        return;
    }

    s = *(--first_free);
    alloc.destroy(first_free);
}

接下來實現測試函數,測試上述操作

void test_strvec()
{
    auto str1 = StrVec();
    str1.push_back("hello zack");
    StrVec str2(str1);
    str2.push_back("hello rolin");
    StrVec str3 = str1;
    string strtmp;
    str3.pop_back(strtmp);
}

在主函數調用上面test_strvec,運行穩定。

總結

本文通過allocator實現瞭一個類似於vector的類,管理string變量。演示瞭拷貝構造,拷貝賦值要註意的事項,同時演示瞭如何手動開辟內存並管理內存空間。

到此這篇關於C++中allocator類使用示例的文章就介紹到這瞭,更多相關C++ allocator類內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: