美文网首页
C++内存篇(四):写一个类似vector的简单动态内存管理类

C++内存篇(四):写一个类似vector的简单动态内存管理类

作者: wangawu121 | 来源:发表于2020-04-27 22:10 被阅读0次

动态内存管理类

某些类在运行时分配可变大小的内存空间,这种类如果可以的话应该使用标准容器库来保存它的数据。例如用一个vector来管理其底层内存。

但是有些类却需要自己进行内存分配,这样就必须定义自己的拷贝控制成员来管理内存分配。拷贝控制成员包括:

  • 拷贝构造函数
  • 拷贝赋值赋值运算符(即=运算符)
  • 析构函数

下面来实现vector的一个(极度)简化版本,该版本只针对string元素,故将它命名为StrVec

vector的内存分配方法

让我们先回顾一下vector的内存分配方式:

  1. 预先分配足够的内存,将元素保存在连续的内存中
  2. 添加元素的成员函数检查是否有空间容纳更多的元素
  3. 如果有,在下一个可用位置构造新对象
  4. 如果没有空间了,vector将重新分配空间:获得一块新的更大空间,将原有元素放入新空间,并释放旧空间。

设计自己的类

我们在内存分配上采取和vector类似的策略。

我们来看看我们需要什么类成员来完成一个类似vector的行为:

公有成员

1、关于构造、析构和拷贝赋值的

  1. 默认构造函数:

    StrVec()
    
  2. 三个拷贝控制成员:拷贝构造函数、拷贝赋值运算符、析构函数

    StrVec(const StrVec&)                //拷贝构造函数
    StrVec& operator=(const StrVec&) //拷贝赋值运算符
    ~StrVec()                            //析构函数
    

2、其他功能

void push_back(const string&)       //拷贝元素
size_t size() const //返回当前存储着多少个元素
size_t capacity() const //返回当前这个类拥有的内存可以放多少个元素
string* begin() const //返回当前用来存放元素的内存块的首地址
string* end() const //返回当前用来存放元素的内存块的第一个空闲地址

私有成员

1.关于内存控制的

  1. 内存分配器:

    因为直到主程序结束前我们可能一直需要内存分配器来完成一些功能,所以它需要是一个静态变量。

    (静态变量一旦声明就会直到主程序结束才被销毁)

    static std::allocator<std::string> alloc //用来给该类分配一块string类型的内存
    
  2. 一个检查是否还有剩余空间、如果没有就重新分配内存的函数

    void chk_n_alloc()
    
  3. 用于重新分配内存的函数

    void reallocate()
    
  4. 分配空间并接受参数拷贝其他内存上的对象的函数:

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

    alloc_n_copy接受两个string指针,分别是要拷贝的string的内存块首地址和尾地址,返回值是一个pair,包含为新分配的内存首地址和末地址。

  5. 销毁元素并释放内存的函数:

    free()
    
  6. 重新分配内存:chk_n_alloc()检测到空间不够时调用它,获得一块更大的内存,并把原来的对象移动到上面。我们使用move来完成移动而不是拷贝,如果使用拷贝我们需要把旧的对象拷贝到新的更大的内存上,再释放原有的内存,这样浪费了一遍构造对象过程,而如果使用移动,我们会让原有内存上的对象直接去管理新的内存而不用再构造它们,然后同样会释放原有内存。

  7. 一些标记内存位置的成员:

string* elements;   //指向被分配的内存的首地址
string* first_free; //指向数组第一个空闲元素的指针
string* cap;        //指向被分配的内存的末地址后一位
image-20200427222101809.png

代码

#include<iostream>
#include<utility>
using namespace std;

//动态内存管理类
class StrVec {
public:
    //___________________关于构造、析构和拷贝赋值的________________________
    StrVec() : //allocator成员默认初始化
        elements(nullptr), first_free(nullptr), cap(nullptr) {}
    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 std::allocator<std::string> alloc; //分配元素
    //被添加元素的函数所使用
    void chk_n_alloc() {
        if (size() == capacity()) reallocate();
    }
    //工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
    pair<string*, string*> alloc_n_copy(const string*, const string*);
    void free();        //销毁元素并释放内存
    void reallocate();  //获得更多内存并拷贝已有元素
    string* elements;   //指向数组首元素的指针
    string* first_free; //指向数组第一个空闲元素的指针
    string* cap;        //指向数组尾后位置的指针
};

//___________________________内存控制___________________________________
//alloc_n_copy:
//参数为拷贝对象的首地址和末地址
//返回值为新分配的内存首地址和末地址
pair<string*,string*>
StrVec::alloc_n_copy(const string* b, const string* e) {
    //分配空间保存给定范围中的元素
    auto data = alloc.allocate(e - b);
    //初始化并返回一个pair
    //该pair由data和uninitialized_copy的返回值
    //(该返回值是一个指针),指向最后一个构造元素的位置
    //uninitialized_copy以指针data为首地址,把内存地址b到内存地址e上的对象拷贝到data这里
    return { data, uninitialized_copy(b,e,data) };
}

void StrVec::push_back(const string& s){
    chk_n_alloc(); //确保有空间容纳新元素
    //再first_free指向的元素中构造s的副本
    alloc.construct(first_free++, s);
}

void StrVec::free() {
    //不能传递给deallocate一个空指针,如果elements为0,函数什么也不做
    if (elements) {
        //逆序销毁元素
        for (auto p = first_free; p != elements;)
            alloc.destroy(--p);
        alloc.deallocate(elements, cap - elements);
    }
}

//___________________________拷贝控制成员_______________________________
//拷贝构造函数
StrVec::StrVec(const StrVec& s) {
    //调用alloc_n_copy分配空间以容纳与s中一样多的元素
    auto newdata = alloc_n_copy(s.begin(),s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

//析构函数:调用free
StrVec::~StrVec() { free(); }

//拷贝赋值运算符
StrVec &StrVec::operator=(const StrVec &rhs){
    //调用alloc_n_copy分配内存,大小与rhs中元素占用空间一样多
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::reallocate() {
    //分配当前大小两倍的内存空间
    //如果原本没有空间,则分配容纳一个元素的空间
    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)
        //调用move会使construct使用string的移动构造函数而不是拷贝构造函数
        alloc.construct(dest++,std::move(*elem++));
    //移动完元素就释放旧空间
    free();
    //更新数据结构,执行新单元
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

相关文章

  • C++内存篇(四):写一个类似vector的简单动态内存管理类

    动态内存管理类 某些类在运行时分配可变大小的内存空间,这种类如果可以的话应该使用标准容器库来保存它的数据。例如用一...

  • C++中New关键字和命名空间

    动态内存分配 C++中的动态内存分配C++中通过new关键字进行动态内存申请C++中的动态内存申请是基于类型进行的...

  • C++中的新成员

    C++中的动态内存分配 C++中通过new关键字进行动态内存申请 C++中的动态内存申请是基于类型进行的 dele...

  • 十、动态内存分配与命名空间

    1、C++中的动态内存分配 通过new关键字进行动态内存申请 C++中的动态内存申请时基于类型进行的 delete...

  • C++ 动态内存

    原文地址:C++ 动态内存 了解动态内存在 C++ 中是如何工作的是成为一名合格的 C++ 程序员必不可少的。C+...

  • linux c/c++面试知识点整理(一)

    1、c/c++申请动态内存 在c++中,申请动态内存是使用new和delete,这两个关键字实际上是运算符,...

  • c++如何实现 STL 中的vector

    前言 面试中可能会考你,怎么去实现一个vector呢?这需要了解vector的底层实现。在这之前,需要学习动态内存...

  • 《C++ Primer Plus》第12章学习笔记

    类和动态内存 1. 动态内存和类 本章先从一个错误的字符串类设计来揭示在C++类设计中可能存在的问题,特别是在使用...

  • nginx 动态数组

    ngx_array_t动态数组类似于C++语言STL库的vector容器,它用连续的内存存放着大小相同的元素(就像...

  • C++第五弹---堆与拷贝构造函数

    动态内存分配 关于堆C++程序的内存格局通常分为四个区:全局数据区、代码区、栈区、堆区 全局变量、静态数据、常量存...

网友评论

      本文标题:C++内存篇(四):写一个类似vector的简单动态内存管理类

      本文链接:https://www.haomeiwen.com/subject/ywspwhtx.html