完整代码示例

来源:互联网 发布:计算面积的软件 编辑:程序博客网 时间:2024/06/04 19:49

完整代码示例

#ifndef LEFTVALUE_STRVEC_H#define LEFTVALUE_STRVEC_H#include <bits/allocator.h>#include <string>#include <memory>class StrVec {public:    //  默认构造函数。    // (隐式地)默认初始化静态成员allocator1。    // (显示地)将指针成员初始化为nullptr,表示没有元素。    StrVec():element(nullptr), first_free(nullptr), cap(nullptr)    {    }    StrVec(const StrVec &obj)    {        auto newdata = alloc_n_copy(obj.begin(), obj.end());        element = newdata.first;        first_free =  newdata.second;        //由于alloc_n_copy分配的内存空间恰好容纳给定的元素,cap也指向最后一个构造的函数之后的位置。        cap = newdata.second;    }    StrVec& operator=(const StrVec &obj)    {        auto data = alloc_n_copy(obj.begin(), obj.end());        free();        element = data.first;        first_free = cap = data.second;        return *this;    }    //与拷贝构造函数不同,移动构造函数不分配任何新内存;    //它接管给定的StrVec中的内存。    //在接管内存之后,它将给定对象中的指针都置为nullptr。    //这样就完成了从给定对象的移动操作,此对象将继续存在。    //最终,移动源对象会被销毁,意味着将在其上运行析构函数。    //Strvec的析构函数在first_free上调用deallocate。    //如果我们忘记了改变obj.first_free,则销毁移动后源对象就会释放掉我们刚刚移动的内存。    StrVec (StrVec &&obj) noexcept : element(obj.element), first_free(obj.first_free), cap(obj.cap)    {        obj.element = obj.first_free = obj.cap = nullptr;    }    StrVec&operator=(StrVec &&obj) noexcept    {        //直接检查this指针与obj的地址是否相同。        //如果相同,右侧和左侧运算对象指向相同的对象,我们不需要做任何事情。        //否则,我们释放左侧运算对象所使用的内存,并接管给定对象的内存。        //我们进行检查的原因是此右值可能是move调用的返回结果。        //我们不能在使用右侧运算对象的资源之前就释放左侧运算对象的资源(可能是相同的资源)。        if (this != &obj)        {            free();            element = obj.element;            first_free = obj.first_free;            cap = obj.cap;            obj.element = obj.first_free = obj.cap = nullptr;        }        return *this;    }    ~StrVec()    {        free();    }    //当我们调用push_back,实参类型决定了新元素是拷贝还是移动到容器中。    //这些调用的差别在于实参是一个左值还是一个右值。    //如果一个成员函数同时提供了拷贝和移动版本。    //第一个版本接受一个指向const的左值引用,我们可以将能转换为std::string类型的任何对象传递给第一个版本的push_back。    //此版本从其参数拷贝数据。    //第二版版本接受一个指向非const的右值引用。我们只可以传递给它非const的右值。    //此版本对于非const的右值是精确匹配的,因此当我们传递一个可修改的右值时,编译器会选择运行这个版本。    //此版本会从其参数窃取数据。    //一般来说,我们不需要为函数操作定义接受一个const X&&或是一个普通的X&参数的版本。    //当我们希望从实参窃取数据时,通常传递一个右值引用,为了达到这一目的,实参不能是const的。    //类似的,从一个对象进行拷贝的操作不应该改变该对象,因此通常不需要定义一个接受一个普通的X&参数的版本。    void push_back(const std::string &string1)    {        //调用chk_n_alloc确定有空间容纳新元素。        //如果需要,chk_n_alloc会调用reallocate。        chk_n_alloc();        allocator1.construct(first_free++, string1);    }    void push_bakc(std::string &&string2)    {        chk_n_alloc();        allocator1.construct(first_free++, std::move(string2));    }    size_t size() const    {        return first_free - element;    }    size_t capacity() const    {        return cap - element;    }    std::string* begin() const    {        return element;    }    std::string* end() const    {        return first_free;    }private:    void chk_n_alloc()    {        if (size() == capacity())            reallocate();    }    std::pair<std::string*, std::string*> alloc_n_copy(const std::string *b, const std::string *e)    {        //用尾后指针减去首元素指针,来计算需要多少空间。        auto data = allocator1.allocate(e - b);        //pair类型对象的first成员指向分配的内存的开始位置。        //second成员则是uninitialized_copy的返回值,此值是一个指针,指向最后一个构造元素之后的位置。        return {data, std::uninitialized_copy(b, e, data)};    };    void free()    {        //我们传递给deallocate的指针必须是之前某次调用allocate调用所返回的指针。        //因此,在调用deallocate之前我们首先检查element是否为空。        if (element)        {            //调用allocator的destory成员,从构造的尾元素开始,到首元素为止,逆序销毁所有元素。            //destory函数会运行string的析构函数。string的析构函数会释放string自己分配的内存空间。            for (auto p = first_free; p != element; )                allocator1.destroy(--p);            //调用deallocate来释放本StrVec对象分配的内存空间。            allocator1.deallocate(element, cap - element);        }    }    void reallocate()    {        //首先调用allocate分配新内存空间。        //我们每次重新分配内存时都会将StrVec的容量加倍。        //如果StrVec为空,我们将分配容纳一个元素的空间。        auto newcapacity = size() ? 2*size() : 1;        auto newdata = allocator1.allocate(newcapacity);        //dest指向构造新string的内存        //elem指向原数组中的元素        auto dest = newdata;        auto elem = element;        //for循环遍历每个已有元素,并在新内存空间中construct一个对应元素        //我们每次用后置递增运算符将dest(和elem)推进到各自数组中的下一个元素。        //调用move返回的结果会令construct使用string的移动构造函数。        //由于我们使用了移动构造函数,这些string管理的内存将不会被拷贝。        //相反,我们构造的每个string都会从elem指向的string那里接管内存的所有权。        for (size_t i = 0; i != size(); ++i)            allocator1.construct(dest++, std::move(*elem++));        //元素移动完毕后,调用free销毁旧元素并释放StrVec原来使用的内存。        //我们不知道旧StrVec内存中的string包含什么值,但我们保证对它们执行string的析构函数是安全的。        free();        //更新指针        element = newdata;        first_free = dest;        cap = element + newcapacity;    }    static std::allocator<std::string> allocator1;    std::string *element;//指向数组首元素    std::string *first_free;//指向数组第一个空闲单元的元素    std::string *cap;//指向数组尾后位置};#endif //LEFTVALUE_STRVEC_H
原创粉丝点击