C++ Primer第十三章⑤

C++ Primer

拷贝控制

动态内存管理类

某些类需要在运行时分配可变大小的内存空间,这种类最好在底层使用标准容器库,例如我们的StrBlob类使用vector。
但是,有些类需要自己进行内存分配,它基本就要定义自己的拷贝控制成员来管理所分配的内存。

我们在这一节将要实现标准库vector类的一个简化版本,它只能用于string,命名为StrVec。

StrVec类的设计

主要参照vector<string>来。
我们将使用allocator来获得原始内存,由于它分配的内存是未构造的,我们将需要在添加新元素是用allocator的construct成员在原始内存中创建对象;同样的,我们在删除元素时就使用destroy成员来销毁函数。
每个StrVec有三个指针成员指向其元素所使用的内存:

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

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

  1. alloc_n_copy会分配内存,并拷贝一个给定范围内的元素
  2. free会销毁构造的元素并释放内存
  3. chk_n_alloc保证StrVec至少有容纳一个新元素的空间,如果空间不够的话,它会调用reallocate来分配更多内存
  4. reallocate在内存用完时为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 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; //分配元素

    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;
};

该说明的都已经在注释中说明了。

接下来分别取实现已经声明的函数定义:

push_back
void StrVec::push_back(const string& s)
{
    chk_n_alloc(); //确保有空间
    alloc.construct(first_free++, s); //调用allocator成员construct来插入
    //至于construct怎么搞得咱们就不了解了,有兴趣自己去查把
}
alloc_n_copy

分配足够的内存来保存给定范围的元素,并将这些元素拷贝到新分配的内存中:

pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e)
{
    auto data = alloc.allocate(e-b); //分配正好的空间
    return {data, uninitialized_copy(b, e, data)};
}

在返回语句中完成拷贝工作:
返回语句中对返回值进行了列表初始化,返回的pair中,first指向分配内存的开始位置(因为data作为名字来用就是首元素地址吧),second是uninitialized_copy的返回值,这个值是一个指向尾后元素的指针。

free

free要干两件事:

  1. destroy元素,是指确实有内容的元素
  2. 释放分配的内存空间,包括没有元素的内存
    void StrVec::free()
    {
    if(elements) //确保不是空指针,就是要有元素
    {
        for(auto p = first_free; p != elements;) //逆序的哦(为啥要逆序我不知道)
        //可能是为了重用这部分空间,删除好了之后指针指向首元素
        {
            alloc.destroy(--p);
        }
        alloc.deallocate(elements, cap-elements);
    }
    }
    
拷贝控制成员

有了前面的工具函数,实现拷贝控制成员很简单:

StrVec::StrVec(const StrVec &s) //拷贝构造函数
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = newdata.sevond
}

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

StrVec &StrVec::operator=(const StrVec &rhs)
{
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = data.first;
    first_free = cap = data.second; //都等于
    return *this;
}
reallocate

我们会用到一些之后要学的函数:

void StrVec::reallocate()
{
    auto newcapacity = size() ? 2*size() : 1;
    //空就分配一个,不空就变为2倍,好好看看,这个写法很装逼哦

    auto newdata = alloc.allocate(newcapacity); //分配新内存

    auto dest = newdata; //指向新数组的下一个空闲位置
    auto elem = elemments; //指向旧数组的下一个元素

    for(size_t i=0; i != size(); ++i)
    {
        alloc.construct(dest++, move(*elem++));
        //这里的move函数你就理解为把旧数组元素移动到新数组中,不需要拷贝了
    }
    free(); //移动好元素就释放旧内存空间

    //更新数据成员
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

写个东西还真是不容易。

#C++工程师#
全部评论

相关推荐

06-11 13:34
门头沟学院 C++
offe从四面八方来:我真的没时间陪你闹了
点赞 评论 收藏
分享
迟缓的斜杠青年巴比Q了:简历被投过的公司卖出去了,我前两天遇到过更离谱的,打电话来问我有没有意向报班学Java学习,服了,还拿我学校一个学长在他们那报班学了之后干了华为OD当招牌
点赞 评论 收藏
分享
评论
3
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务