C++中allocator类使用示例

目录
  • 动态内存管理
  • StrVec类实现细节
  • 总结

动态内存管理

之前我们讲述过动态内存的开辟,可以通过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类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈C++空间配置器allocator

    目录 概述 1. Allocator 的标准接口 2. SGI STL 内存分配失败的异常处理 3. SGI STL 内置轻量级内存池的实现 4. SGI STL 内存池在多线程下的互斥访问 概述 在C++中,一个对象的内存配置和释放一般都包含两个步骤,对于内存的配置,首先是调用operator new来配置内存,然后调用对象的类的构造函数进行初始化:而对于内存释放,首先是调用析构函数,然后调用 operator delete进行释放. 如以下代码: class Foo { ... }; Foo

  • C++中std::allocator的使用案例详解

    标准库中包含一个名为allocator的类,允许我们将分配和初始化分离.使用allocator通常会提供更好的性能和更灵活的内存管理能力.         new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起.类似的,delete将对象析构和内存释放组合在了一起.我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起.因为在这种情况下,我们几乎肯定知道对象应有什么值.当分配一大块内存时,我们通常计划在这块内存上按需构造对象.在此情况下,我们希望将内存分配和对象构造

  • C++中allocator类使用示例

    目录 动态内存管理 StrVec类实现细节 总结 动态内存管理 之前我们讲述过动态内存的开辟,可以通过new, malloc,以及alloc等方式,本文通过介绍alloc方式,构造一个StrVec类,这个类的功能类似于一个vector,实现字符串的管理,其中包含push一个字符串,动态扩容,析构,回收内存等操作. StrVec类实现细节 StrVec类实现如下 class StrVec { public: //无参构造函数 StrVec() : elements(nullptr), first_

  • Python中的类对象示例详解

    抽象特点 Python 一切皆对象,基于此概念,对 类 class 有以下特点: 类与实例的属性 类对象创建可选择定义类属性,创建实例对象时,实例属性自动执行类的__init__方法初始化 实例对象自动继承相应的类属性(如果有),但实例属性优先级更高 实例方法,类方法,静态方法的参数 实例方法是一般函数但实例方法需要传入self参数(与一般函数的区别) 类方法和静态方法是通过装饰器实现的函数,类方法需要传入cls参数,静态方法无需传入self参数或者是cls参数(但也能传入参数) 其中self参

  • CI框架中zip类应用示例

    CI框架自带的zip类简单实用,本文就来简单说一下CI框架的zip类的使用. 首先需要导入zip类: 复制代码 代码如下: $this->load->('zip'); 此外,但是考虑到CI的结构,还需要设置一下自动加载类,把zip加载进去.也可以在设置的时候,统一加载.大家如果遇到这个问题可能就是自动加载没有加载zip类.具体调用方法如下: 复制代码 代码如下: $path = $_SERVER['DOCUMENT_ROOT'];//需要压缩的文件夹路径$this->zip->re

  • C++中的类模板详解及示例

    C++中的函数模板 对于类的声明来说,也有同样的问题.有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类: 复制代码 代码如下: class Compare_int{ public:  Compare(int a,int b)  {   x=a;   y=b;  }   int max()  {   return (x>y)?x:y;  }  int min()  {   return (x<y)?x:y;  } private:  int x,y;}; 其作用是

  • Python中的类与类型示例详解

    1.经典类与新式类 在了解Python的类与类型前,需要对Python的经典类(classic classes)与新式类(new-style classes)有个简单的概念. 在Python 2.x及以前的版本中,由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于"新式类",都会获得所有"新式类"的特性:反之,即不由任意内置类型派生出的类,则称之为"经典类". "新式类"和"经典类"的区

  • 在Spring Boot中从类路径加载文件的示例

    资源加载器 使用Java,您可以使用当前线程的classLoader并尝试加载文件,但是Spring Framework为您提供了更为优雅的解决方案,例如ResourceLoader. 您只需要自动连接ResourceLoader,然后调用getResource(„somePath")方法即可. 在Spring Boot(WAR)中从资源目录/类路径加载文件的示例 在以下示例中,我们从类路径中加载名为GeoLite2-Country.mmdb的文件作为资源,然后将其作为File对象检索. @Se

  • Java中枚举类的用法示例详解

    目录 1.引入枚举类 2.实现枚举类 3.枚举类的使用注意事项 4.枚举的常用方法 5.enum细节 1.引入枚举类 Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等. Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割. 示例: enum Color { RED, GREEN, BLUE; } 2.实现枚举类 接下来我们来看一个一个简单的DEMO示例: /** * java枚举 */ p

  • ES6中定义类和对象的方法示例

    本文实例讲述了ES6中定义类和对象的方法.分享给大家供大家参考,具体如下: 类的基本定义和生成实例: // 类的基本定义和生成实例 class Parent{ //定义一个类 constructor(name='xiaxaioxian'){ this.name= name; } } // 生成一个实例 let g_parent = new Parent(); console.log(g_parent); //{name: "xiaxaioxian"} let v_parent = ne

  • thinkPHP5.1框架中Request类四种调用方式示例

    本文实例讲述了thinkPHP5.1框架中Request类四种调用方式.分享给大家供大家参考,具体如下: 1. 传统调用 访问方式:http://127.0.0.1/demo/demo3/test?name=kk&age=22 <?php /** * Created by PhpStorm. * User: 10475 * Date: 2018/8/27 * Time: 22:59 */ namespace app\demo\controller; use think\Request; cl

  • Java8中Optional类型和Kotlin中可空类型的使用对比

    本文主要给大家介绍了关于Java8中Optional类型和Kotlin中可空类型使用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在 Java 8中,我们可以使用 Optional 类型来表达可空的类型. package com.easy.kotlin; import java.util.Optional; import static java.lang.System.out; /** * Optional.ofNullable - 允许传递为 null 参数 *

随机推荐