3.简单的空间配置器allocator

文章介绍了C++中如何通过分离组件来提高代码复用性和可读性,特别是通过实现一个简单的空间配置器(allocator)来封装内存的申请和释放,以及对象的构造和析构。文中提供了具体的模板函数和类实现,包括allocate、deallocate、construct和destory,并讨论了C++中分配零字节内存的特殊行为。此外,还展示了如何在vector类中应用这些概念。
摘要由CSDN通过智能技术生成

参考原文

为了提高代码的复用性和可读性,我们可以考虑分离出一个专门的组件,负责内存的管理(目前只有申请和释放)和对象的构造和析构。这就是STL六大组件中的空间配置器(allocator)。

目前,我们先实现一个最简单的空间配置器,空间配置部分简单封装 ::operator new 和 ::operator delete 即可,对象构造则是简单封装定位new,析构则是简单调用类的析构器即可。

请建立两个头文件,allocator.h 和 constructor.h,分别实现两个部分。

问题:

静态函数

在C++中,静态函数(static function)是指在类中声明为静态的成员函数。静态函数与普通成员函数不同,它不需要通过对象来调用,而是可以直接通过类名来调用。静态函数可以访问类的静态成员变量和静态成员函数,但不能访问非静态成员变量和非静态成员函数。

c++分配0个内存空间

在 C++ 中,如果使用 new 运算符或者 malloc() 函数分配零个字节的内存空间,通常会返回一个非空指针。

这是因为在 C++ 中,动态内存分配函数不允许返回空指针(即 nullptr),除非请求的内存大小为零。如果请求的内存大小为零,这些函数仍然会分配一个最小的有效内存块,并返回一个指向该内存块的指针。

具体来说,如果使用 new 运算符分配零个字节的内存空间,它将分配一个大小为 1 字节的内存块,并返回一个指向该内存块的指针。这个指针可以被用来访问这个内存块,但是在这个内存块中不能存储任何数据。

以下是一个示例,演示了如何使用 new 运算符分配零个字节的内存空间:

int* ptr = new int[0];  // 分配零个整数的内存空间

在这个示例中,new 运算符将分配一个大小为 1 字节的内存块,并返回一个指向该内存块的指针。由于请求的内存大小为零,这个指针不能用来存储任何数据。

需要注意的是,如果使用 delete 运算符或者 free() 函数来释放这个指针指向的内存空间,可能会导致未定义行为。这是因为这个指针指向的内存块并不是通过正常的动态内存分配函数分配的,而是通过一个特殊的处理方式分配的。因此,建议避免分配零个字节的内存空间。

示例:

#include "iostream"

using namespace std;

int main() {
    int *p = new int[0];
    p[0] = 1;
    cout << p[0] << endl;
    cout << p << endl;

    return 0;
}

//输出:
1
0x600000dfc000

//只是对::operator new 和::operator delete 进行简单的封装,效率不高
#include <new>
#include <iostream>

//修改size的类型为size_t,能够管理(目前只有分配和回收)更大的内存,int类型相对来说太小了。

//定义简单的空间分配和析构模版函数
template<typename T>
inline T *allocate(size_t size, T *) {
    std::set_new_handler(0);                            //错误处理函数,参数为零,分配失败直接返回
    T *tmp = (T *) ::operator new(size * sizeof(T));
    if (tmp == 0) {
        std::cerr << "Failed to allocate memory" << std::endl;      //cerr用于向标准错误输出流(通常是控制台)输出错误信息
        exit(1);
    }
    return tmp;
}

template<typename T>
inline void deallocate(T *buffer) {

    ::operator delete(buffer);

}

template<typename T>
class allocator {
public:
    typedef size_t size_type;
    static T *allocate(size_type n) {             //定义为类内静态成员函数,可以直接通过类名调用
        return ::allocate(n, (T *) 0);      //(T*)0是一个空指针,用于指定数组中元素的类型
    }

    static void deallocate(T *buffer) {
        ::deallocate(buffer);
    }
};

//定义了全局函数,construct() 和destory(),负责对象的构造和析构
template<typename T, typename Value_type>
void construct(T *p, const Value_type &value) {       //参数不发生改变,用const修饰,value没有参数会自动调用默认构造
    new(p) T(value);        //placement new ,在指针P构造T类型数据,如果Value_type类型也是T类型的,调用拷贝构造
    //如果Value_type不是T类型的,调用T的有参构造
}

template<typename T>
void construct(T *p) {
    new(p) T();             //调用默认构造
}

template<typename T>
void destory(T *p) {
    p->~T();
}
#include "type_traits.h"
#include "allocator.h"
#include "constructor.h"

template<typename T>
class vector {
public:
    /***************嵌套类型名****************/
    //起别名是为了增加代码的可读性

    typedef T value_type;
    typedef T *pointer;
    typedef const T *const_pointer;
    typedef T &reference;
    typedef const T &const_reference;

    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    /***************嵌套类型名完*******************/

protected:
    // 用T* 类型表示vector的头尾和可用空间的最大值
    pointer start;          // 表示当前空间的头
    pointer finish;         // 表示当前使用空间的尾
    pointer end_of_storage; // 表示当前可用空间的尾
    typedef allocator<T> data_allocator;

    // 辅助函数,作用:用于vector空间`在这里插入代码片`不够时扩容
    // 可用空间为0时,申请更大的空间,并把元素都复制过去
    void allocate_and_copy() {
        // std::cout << "调用allocate_and_copy" << std::endl;

        size_type len = (capacity() != 0 ? capacity() * 2 : 1); // 以容量的2倍为规则扩容
        //pointer temp = (pointer) ::operator new(len * sizeof(T));                      // 创建更大的空间,temp指向可用空间的头
        pointer temp = data_allocator::allocate(len);
        for (size_type i = 0; i < size(); ++i) {
            // typename 的作用是把后面的内容当作一个类型
            if (__type_bool<typename __type_traits<T>::has_trivial_copy_constructor>::sign)
                temp[i] = start[i];
            else {
                //new(temp + i) T(start[i]);
                //在指定空间上构造对象,用的是placement new
                construct(temp + i, start[i]);
                destory(start + i);       //析构原对象
            }

            // placement new 在已分配的特定内存创建对象
            // 这个语法的作用是在 temp+i 这个位置上构造一个类型为 T 的对象,并且该对象的初始值是 start[i]
            /*在手动分配内存并构造对象时,我们需要使用 placement new 语法来构造对象。
            placement new 语法相当于在已经分配的内存中构造对象,它不会再次分配内存,而是直接在指定的内存地址上构造对象。
            在使用 placement new 时,需要传递一个指向已经分配内存的指针,并且不能使用 delete 运算符来释放这块内存,
            而是需要先手动调用对象的析构函数,再释放内存*/
        }

        end_of_storage = temp + len;
        finish = temp + size();

        // delete start; // 回收旧数组的头节点,实际上整个旧数组都被释放了
//        if (!__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign) {
//            start->~T();                //析构函数直接销毁整个旧数组并回收内存
//        }
//        delete[] start;

        //通过空间配置器回收空间
        data_allocator::deallocate(start);
        start = temp;
    }

public:
    // 构造函数:这里用成员初始化列表进行初始化
    vector() : start(nullptr), finish(nullptr), end_of_storage(nullptr) { // 默认构造
        //std::cout << "vector的默认构造" << std::endl;
    }

    // 有参构造:初始化vector的长度为n
    vector(size_type n) {

        //std::cout << "vector的有参构造vector(n)" << std::endl;

        //start = (pointer) ::operator new(n * sizeof(T)); // 创建完空间后强制类型转换
        start = data_allocator::allocate(n);
        finish = start + n;
        end_of_storage = start + n;
        //进行默认初始化
        for (size_type i = 0; i < n; ++i) {
            if (__type_bool<typename __type_traits<T>::has_trivial_default_constructor>::sign)
                start[i] = 0;
            else
                //new(start + i) T(); // 调用T类型的默认构造,placement new
                construct(start + i);
        }
    }

    // 有参构造:初始化vector的长度为n,并将这些位置上的数据全部赋值为value
    vector(size_type n, const T &value) {

        //std::cout << "vector的有参构造vector(n,value)" << std::endl;

        //start = (pointer) ::operator new[](n * sizeof(T));   // 先创建空间
        start = data_allocator::allocate(n);
        finish = start + n; // 因为对数组的数组是从0开始存放的,start+n表示的是使用空间的下一个位置
        end_of_storage = start + n;
        for (size_type i = 0; i < n; ++i) // 初始值设为0
        {
            if (__type_bool<typename __type_traits<T>::has_trivial_copy_constructor>::sign)
                start[i] = value;
            else
                //new(start + i) T(value);
                construct(start + i, value);
        }
    }

    // 拷贝构造
    vector(const vector &another) {
        //std::cout << "vector的拷贝构造函数" << std::endl;

        // 创建一个新空间存放another的数据
        size_type len = another.size();
        //start =(pointer) ::operator new[](len * sizeof(T));      //函数原型:void *operator new[](size_t);    size_t是存储数组中所有元素所需的空间
        start = data_allocator::allocate(len);
        for (size_type i = 0; i < len; ++i) {
            // start[i] = *(another.begin() + i);        //const对象不能调用非const成员函数
            start[i] = another[i];
        }
        finish = start + len;
        end_of_storage = finish;
    }

    // 析构函数
    ~vector() {
        //std::cout << "vector的析构函数" << std::endl;
        if (!__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign) {
            for (size_type i = size() - 1; i >= 0; --i) {
                //(start + i)->~T();      //调用析构函数会销毁对象,但不会释放内存
                destory(start + i);
            }
        }
        //::operator delete[](start);
        data_allocator::deallocate(start);
    }

    // 常用接口
    /**********************首尾******************************/

    // 给出vcetor当前使用空间的起始位置
    pointer begin() const {
        return start;
    }

    // 给出vector当前使用空间的下一个位置
    pointer end() const {
        return finish;
    }

    // 逆序遍历时,使用空间的起始位置
    pointer rbegin() {
        return end() - 1;
    }

    // 逆序遍历时,使用空间的结束位置
    pointer rend() {
        return begin() - 1;
    }

    /**********************随机访问******************************/

    // vector中第一个数据
    T &front() {
        return *begin(); // 尽量避免直接使用和暴露成员变量,保证类的封装性
    }

    // vector中最后一个数据
    T &back() {
        return *(end() - 1);
    }

    T &operator[](size_type n) // 返回引用,便于修改
    {                    // 不考虑n越界的情况
        return *(begin() + n);
    }

    T &operator[](size_type n) const // 返回引用,便于修改
    {                          // 不考虑n越界的情况
        return *(begin() + n);
    }

    /**********************大小******************************/

    // 此处const的作用:
    // 一、将成员函数声明为const,表明该函数不会对对象进行修改
    // 二、可以允许在const对象上调用该函数
    // vector中当前有多少个数据
    size_type size() const {
        return finish - start;
    }

    // 容量:vector中当前最多能存放多少个数据
    size_type capacity() const {
        return int(end_of_storage - begin());
    }

    size_type max_size() const {
        return end_of_storage - start;
    }

    // vector当前是否为空
    bool empty() const {
        return begin() == end();
    }

    /**********************  增删改  ******************************/
    // 在vector的末尾插入一个数据
    void push_back(const T &x) {
        // 容器内空间不够,就扩容
        if (finish == end_of_storage) { // 如果长度为零,初始空间设为1
            // std::cout << "capacity()的值为:" << capacity() << std::endl;
            allocate_and_copy();
        }
        if (__type_bool<typename __type_traits<T>::has_trivial_copy_constructor>::sign)
            *finish = x;
        else {
            //new(finish) T(x); // placement new
            //*end() = x;       //ERROR:因为end()是静态成员函数,不能修改对象
            construct(finish, x);
        }
        ++finish;
    }

    // 在vector的末尾删除一个数据
    void pop_back() {
        //if (__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign)
        --finish;
        if (!__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign) {
            //finish->~T(); // 在finish指向的对象上调用析构函数,应该是先--finish再析构吧
            destory(finish);
        }

    }

    // 删除position所指向的数据
    void erase(pointer position) {
        // 把指针后面的全部前移
        for (pointer i = position; (i + 1) != end(); ++i) {
            if (__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign)
                *i = *(i + 1);
            else {
                //i->~T();
                //new(i) T(*(i + 1));
                destory(i);
                construct(i, *(i + 1));
            }
        }
        --finish;
    }

    // 删除从first到last这一段数据,不释放内存
    void erase(pointer first, pointer last) {
        difference_type diff = last - first;
        size_type count = 0;                              // 计数器
        for (pointer i = last; i != end(); ++i, ++count) // 如果i等于end(),就不用移动后面的数据了,直接更新finish就行
        {
            if (__type_bool<typename __type_traits<T>::has_trivial_destructor>::sign)
                *(first + count) = *(first + diff + count); // 这里等号右边必须是first + diff,刚好是last的下一个位置,计数器从0开始遍历正好
            else {
//                (first + count)->~T();
//                new(first + count) T(*(first + diff + count));
//                (first + diff + count)->~T();
                destory(first + count);
                construct(first + count, *(first + diff + count));
                destory(first + diff + count);
            }
        }
        finish = finish - diff; // 指针向前移动
    }

    // 调整vetor的容量,如果调整后的容量大于调整前,则用数据x填充空余部分;
    // 如果调整后容量小于调整前,则只保留前new_size位数据
    void resize(size_type new_size, const T &x) {
        if (new_size < size()) {
            erase(begin() + new_size, end());
        } else {
            //std::cout << "capacity()的值为:" << capacity() << std::endl;
            // 考虑需要扩容的情况
            while (new_size > capacity()) { // 数组是从0开始存储的,所以如果new_size = capacity()的情况刚好能存放

                allocate_and_copy();
                //std::cout << "capacity()的值为:" << capacity() << std::endl;
            }
            for (size_type i = 0; i < new_size - size(); ++i) {
                if (__type_bool<typename __type_traits<T>::has_trivial_copy_constructor>::sign)
                    *(finish + i) = x;
                else {
                    //new(finish + i) T(x);
                    construct(finish + i, x);
                }
            }
            finish += new_size - size();
        }
    }

    // 调整后的容量大于调整前,则用默认数据填充空余部分,其余上同
    void resize(size_type new_size) {
        // 不管是不是POD,都要用默认构造
        resize(new_size, 0);
    }

    // 清空vector中所有数据
    void clear() {
        erase(begin(), end());
    }
};
#include <iostream>
#include "../include/My_vector.h"

//用非POD类型来测试
class myclass {
private:
    int myvalue;
public:
    myclass() : myvalue(0) {}
    myclass(int value) : myvalue(value) {}
    myclass(const myclass &m) {
        this->myvalue = m.getter();
    }
    ~myclass() {}
    int getter() const { return myvalue; }
};

int main() {
    //以下是测试内容和应有结果

    vector<int> &a = *(new vector<int>(60000000, 1));    //初始长度六千万,默认值为1
    std::cout << a.size() << std::endl;    //60000000
    delete &a;

    vector<myclass> &vec = *(new vector<myclass>(4));

    for (int i = 1; i < 10; i++) {
        myclass &temp = *(new myclass(i * 100));
        vec.push_back(temp);
    }

    std::cout << vec.size() << std::endl;                               //13
    std::cout << vec.capacity() << std::endl;                           //16
    std::cout << vec.front().getter() << std::endl;                     //0
    std::cout << vec.back().getter() << std::endl;                      //900

    vec.erase(vec.begin() + 2);                                 //删去的值是300

    std::cout << vec[2].getter() << std::endl;                          //0
    std::cout << vec.size() << std::endl;                               //8

    vec.erase(vec.begin() + 4, vec.end() - 2);                    //删去的值是600,700两个

    std::cout << vec.size() << std::endl;                               //6
    std::cout << vec[4].getter() << std::endl;                          //800

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值