c++笔记-动态内存管理类

  1. 有些类在运行时分配可变大小的内存空间,它们通常可以使用标准库的容器来保存。但是,这一策略并不是对每个类都使用,有些类需要自己对内存进行分配。因此这些类一般通过定义自己的拷贝构造函数来管理分配的内存。
我们使用一个allocator来获得原始内存。由于allocator分配到的内存是未构造的,我们将在需要添加新元素时,
用allocator的construct成员在原始内存中创建对象。当需要删除时,使用destroy来销毁
每个StrVec类中将会有三个指针成员
element指向分配内存中的首元素
first_free指向尾元素后
cap指向分配的内存(包含未使用的)尾后
除了指针成员外,还有一个名为alloc的静态成员,类型为allocator<string>,工具函数如下
alloc_n_copy会分配内存,并拷贝一个给定范围中的元素
free会销毁构造的元素并释放内存
chk_n_alloc保证StrVec至少有容纳一个新元素的空间,如果没有空间容纳一个新元素,则会调用reallocate来分配更多的内存
reallocate在内存用完时为StrVec分配新元素
//实现
class StrVec
{
public:
    StrVec() : element(lullptr), 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 - element; }    //常量函数,指不会改变成员变量的值的函数
    size_t capacity() const { return cap - first_free; }
    std::string* begin() const { return element; }
    std::string* end() const { return first_free; }
private:
    Static std::allocator<string> alloc;
    //要使用allocator必须定义一个它的对象,在定义时指定预计分配的元素类型
    void chk_n_alloc()
        {
            if(size() == capacity())    //调用自身定义的工具函数
                reallocate();
        }
    //类似容器,pair是用来生成特定类型的模板。map的value_type就是一个pair
    std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
    void free();
    void reallocate();
    std::string* element;
    std::string* first_free;
    std::string* cap;
}

默认构造函数(隐式)初始化alloc,(显式)初始化element,first_free和cap,表明没有元素

void StrVec::push_back(const std::string& s)
{
    //确保有空间添加新元素
    chk_n_alloc();
    alloc.construct(first_free++, s);
    //先使用在原first_free处插入s,而后将first_free后移一个元素位置,依旧指向尾后
}
piar<string*, string*> alloc_n_copy(const string* bg, const string* ed)
{
    //分配空间用于保存给定范围的元素
    auto data = alloc.allocate(ed - bg);    //allocate返回的是指向首元素的指针
    //初始化并返回一个pair,该pair由data和uninitialized_copy的返回值构成
    return (data, uninitialized_copy(b, e, data));
    //uninitialized_copy返回指向最后一个构造元素之后的位置。
}
void StrVec::free()
{
    //注意不能传递给deallocate一个空指针,如果element为0,则函数不需执行
    if(element)
    {
        //destroy是逆序销毁创建的元素
        for(auto p = first_free; p != element; /*空*/)
            alloc.destroy(--p);
            //destroy函数是对p指向的元素进行析构,因此先p减一使得p指向最后一个元素,而不是尾后
        alloc.deallocate(element, cap - element);
        //调用deallocate来释放本StrVec对象分配的空间
    }
}

在实现了alloc_n_copy和free函数后,类的拷贝控制成员实现就相对简单
StrVec::StrVec(const StrVec& s)
{
    //调用alloc_n_copy分配空间以容纳与s中一样夺得元素
    auto  newdata = alloc_n_copy(s.begin(), s.end());//使用begin和end将底层返回element和first_free隐藏了
    element = newdata.first;
    first_free = cap = newdata.second;
    //alloc_n_copy返回的是一个pair。其first成员指向第一个构造的元素,second指向最后一个元素尾后
    //由于函数分配的空间恰好是容纳给定的元素,因此cap和first_free指向相同的位置
}
StrVec::~StrVec()
{
    //析构函数
    free();
}
StrVec::operator=(const StrVec& rhs)
{
    //调用alloc_n_copy分配内存,大小与rhs中内存占用空间一样多
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    element = data.first;
    first_free = cap = data.second;
    return *this;
}

在实现reallocate函数时,我们需要做的是移动而不是拷贝元素
{
    在设计reallocate函数时,我们思考一下函数的工作
    1. 为一个新的,更大的string数组分配内存
    2. 在内存空间的前一部分构造对象,保存现有元素
    3. 销毁原空间中的元素,并释放这块内存
    
    但是,为一个StrVec重新分配内存空间会引起从旧内存空间到新内存空间逐个拷贝string
    string由类值行为,当拷贝一个string时,新string和旧string时相互独立的。
    如果是reallocate拷贝StrVec中的string,则拷贝之后,我们就要立即销毁原空间
}
在重新分配内存空间时,我们如果能避免分配和释放string的额外开销,StrVec的性能会提高不少
{
    使用移动构造函数和std::move避免string拷贝
    移动构造函数通常是将资源从给定对象“移动”而不是拷贝到正在创建的对象,同时保证移动后的string仍然保持一个有效的、可析构的状态。对于string可以想象为每个string都有一个指向char数组的指针,而移动构造函数进行了指针的拷贝,而不是分配空间然后拷贝字符。
    move标准库函数,定义在utility头文件中。
    当reallocate在新内存中构造string时,它必须调用move来表示希望使用string的移动构造函数。
    如果漏掉了move的调用,将会使用string的拷贝构造函数,调用时直接调用std::move
}
//来完成reallocate函数
void StrVec::reallocate()
{
    //我们重新分配时,为新空间分配当前空间的两倍
    auto newcapacity = size() ? 2 * size() : 1;
    //分配空间
    auto newdata = alloc.allocate(newcapacity); //返回的是指向第一个元素的位置的指针
    //将数据转移到新的内存空间
    auto dest = newdata;    //指向新数组中下一个空闲位置
    auto elem = element;    //指向就数组中下一个元素
    for(size_t i=0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
        //在新分配的空间,从dest开始,每个元素进行创建,由move表示希望不是元素的拷贝而是移动
    free(); //一旦移动完成后,释放旧空间
    //更新数据结构,执行新元素
    element = newdata;
    first_free = dest;
    cap = element + newcapacity;
}
#############
该处std::move返回值作为construct的第二个参数,调用move返回的结果会令construct使用string的移动构造函数
因此,这些string管理的内存不会被拷贝,构造的每个string都会从elem指向的string那里接管内存的所有权。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值