实现C++STL向量容器vector代码

手写C++ STL向量容器vector的核心实现与优化思考

在C++标准模板库(STL)中,vector是一种动态数组,支持高效的元素访问和动态扩容。本文通过一个简化版vector的实现案例,剖析其核心功能的设计思路,并探讨其潜在问题及改进方向。


一、自定义vector的核心实现

1. 成员变量与构造函数

自定义vector通过三个指针管理底层数组:

  • first:指向数组的起始位置。
  • last:指向最后一个有效元素的后继位置。
  • end:指向数组内存块的后继位置。

构造函数根据初始容量分配内存:

template <typename T>
class Vector {
private:
    T* first;   // 指向数组首元素
    T* last;    // 指向有效元素的后继位置
    T* end;     // 指向内存块末尾

public:
    Vector(size_t size = 10) {
        first = new T[size];
        last = first;
        end = first + size;
    }
};

2. 析构函数

释放内存并重置指针:

~Vector() {
    delete[] first;
    first = last = end = nullptr;
}

3. 拷贝构造函数与赋值运算符

实现深拷贝以避免浅拷贝问题:

// 拷贝构造函数
Vector(const Vector& rhs) {
    size_t cap = rhs.end - rhs.first;
    first = new T[cap];
    last = first + (rhs.last - rhs.first);
    end = first + cap;
    
    for (size_t i = 0; i < size(); ++i) {
        first[i] = rhs.first[i];
    }
}

// 赋值运算符重载
Vector& operator=(const Vector& rhs) {
    if (this != &rhs) {
        delete[] first;
        // 重新分配内存并拷贝数据(与拷贝构造函数逻辑相同)
    }
    return *this;
}

4. 元素操作接口

  • push_back:末尾添加元素,空间不足时扩容。
  • pop_back:删除末尾元素,调整指针。
  • back:返回末尾元素的值。
void push_back(const T& value) {
    if (last == end) expand();
    *last = value;
    last++;
}

void pop_back() {
    if (last > first) last--;
}

T& back() const {
    return *(last - 1);
}

5. 扩容函数expand

以2倍策略扩容,复制旧数据后释放内存:

void expand() {
    size_t old_cap = end - first;
    size_t new_cap = old_cap * 2;
    T* new_first = new T[new_cap];
    
    for (size_t i = 0; i < old_cap; ++i) {
        new_first[i] = first[i];
    }
    
    delete[] first;
    first = new_first;
    last = first + old_cap;
    end = first + new_cap;
}

二、存在的问题分析

1. 内存管理与对象构造的耦合

  • 无效对象构造:构造函数中使用new T[size]会调用每个元素的默认构造函数,即使容器为空。
  • 析构风险:直接delete[]会析构所有元素,但部分元素可能未初始化或已手动析构。

2. 深拷贝效率低下

拷贝构造函数和赋值运算符中逐元素复制数据的复杂度为O(n),对大型容器性能影响显著。

3. 缺乏灵活性

标准库的vector通过**空间配置器(allocator)**分离内存分配与对象构造,而自定义实现未实现这一点,导致以下问题:

  • 无法自定义内存分配策略(如内存池)。
  • 对象构造与析构无法精确控制。

三、改进方向:引入空间配置器(allocator)

1. allocator的核心作用

空间配置器负责四类操作:

  1. 内存分配allocate):仅分配内存,不构造对象。
  2. 内存释放deallocate):仅释放内存,不析构对象。
  3. 对象构造construct):在指定内存地址构造对象。
  4. 对象析构destroy):析构对象,不释放内存。

2. 自定义allocator的实现

template <typename T>
class Allocator {
public:
    T* allocate(size_t n) {
        return static_cast<T*>(malloc(n * sizeof(T)));
    }

    void deallocate(T* p) {
        free(p);
    }

    template <typename... Args>
    void construct(T* p, Args&&... args) {
        new (p) T(std::forward<Args>(args)...);
    }

    void destroy(T* p) {
        p->~T();
    }
};

3. 改造后的vector

template <typename T, typename Alloc = Allocator<T>>
class Vector {
private:
    T* first;
    T* last;
    T* end;
    Alloc alloc; // 空间配置器对象

public:
    Vector(size_t size = 10) {
        first = alloc.allocate(size);
        last = first;
        end = first + size;
    }

    ~Vector() {
        for (T* p = first; p != last; ++p) {
            alloc.destroy(p);
        }
        alloc.deallocate(first);
    }

    void push_back(const T& value) {
        if (last == end) expand();
        alloc.construct(last, value);
        last++;
    }

    void pop_back() {
        if (last > first) {
            last--;
            alloc.destroy(last);
        }
    }
};

四、总结

通过自定义vector的实现,可以深入理解动态数组的核心机制。然而,直接使用newdelete存在内存管理与对象生命周期耦合的问题。引入空间配置器后,实现了以下优化:

  1. 资源管理精细化:分离内存分配与对象构造/析构。
  2. 性能提升:避免无效对象的构造与析构。
  3. 灵活性增强:支持自定义内存分配策略(如内存池)。

后续可进一步研究标准库中allocator的高级实现(如SGI STL的二级空间配置器),以提升容器的性能和灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值