【C++】STL源码仿写(二):vector

一、vector概览

上一篇博客中我们介绍了string的简化实现,今天我们来介绍另一个STL当中比较重要的容器------vector。

private 字段:数据结构

在我们的实现中,vector作为通用容器需要兼容不同的数据/对象类型,所以我们需要用模板这个特性,为此,我们涉及data_字段为T*类型,其中T是模板名,内存中为数组所分配的内存长度是capacity_,实际长度是size_。

public 方法:对外接口

对外接口的实现上,重点考虑push_back()和pop_back()。的实现需要考虑左右值的兼容,为此,我们用到了完美转发、万能引用,还需要考虑扩容的逻辑。pop_back方法的实现比较简单,就是要记得调用容器中对象的析构函数。

Rule of five

我们的vector实现有对堆上的资源进行管理,所以需要rule of five,但是只实现析构函数;由于只使用index找值,且只调用push_back和pop_back,所以拷贝构造、移动构造、拷贝赋值、移动赋值可以都禁止掉。

二、vector实现

我们使用默认构造和默认成员初始化的办法,这样避免过度复杂化实现:

cpp 复制代码
class vector {
public:
vector() = default;
private:
  T* data_ = nullptr;
  size_t size_ = 0;
  size_t capacity_ = 0;
};

现在考虑实现析构函数,析构函数的作用有两个:1.逐个调用容器中对象的析构函数,清空容器;2.释放已分配的内存。这里注意clear()方法是STL vector的公有方法,所以单独拿出来。释放内存的实现使用operator detele,因为之前clear方法已经调用了析构函数了。

cpp 复制代码
  ~vector() {
    clear(); //清空容器
    deallocate(); //释放内存
  }
  void clear() {
    for (size_t i = 0; i < size_; i++) {
      data_[i].~T();
    }
    size_ = 0;
  }

private:
  void deallocate() {
    ::operator delete(data_);
    data_ = nullptr;
    capacity_ = 0;
  }

这里顺便实现一下allocate():注意我们的vector实现中构造与内存分配总是采取分离的策略,所以也使用operator new。operator new返回的类型是void*,所以需要类型转换

cpp 复制代码
  T& allocate() {
    return static_cast<T*>(::operator new(capacity_ * sizeof(T)));
  }

现在重头戏来了,让我们来实现push_back()方法。

push_back要考虑到左右值两种的情况。我们这里使用emplace_back作为统一的入口,用变参模板+完美转发+万能引用保留参数数目和值类别;emplace_back:在扩容后使用placement new原地构造对象。

cpp 复制代码
  void push_back(const T& value) {emplace_back(value);}
  void push_back(T&& value) {emplace_back(std::move(value));}

  template<typename ...Args>//变参模板
  T& emplace_back(Args&&... args) {//万能引用
    if (size_ == capacity_) {
        reallocate(capacity_ == 0 ? 1 : capacity_ * 2);//扩容
    }
    new (data_ + size_) T(std::forward<Args>(args)...);//placement new
    return data_[size_++]; //提前位移,为下一个emplace对象腾位置
  }

现在来实现reallocate这个函数:

  • 分配新内存
  • 复制数据到新内存(placement new)
  • 如果抛异常需要清空新内存的数据
  • 清空原内存数据,释放内存
  • 设置新的cap、sz,data指向新内存
cpp 复制代码
  void reallocate(size_t new_cap) {
    T *new_data = allocate(new_cap); //分配新内存
    size_t new_size = 0;
    try {
      for (; new_size < size_; new_size++) {//placement new + 移动优化
        new (new_data + new_size) T(std::move_if_noexcept(data_[new_size]));
      }
    } catch (...) { //中间抛出异常时清空、释放
      for (size_t i = 0; i < new_size; i++) {
        new_data[i].~T();
      }
      ::operator delete(new_data(i));
      throw; //rethrow,向上传播异常
    }
    clear();
    deallocate();
    size_ = new_size;
    capacity_ = new_cap;
    data_ = new_data;
  }

接下来实现pop_back(),popback比较简单,就是将最后一个元素从data中删去,调析构、size--即可。我们顺便实现back方法,pop_back一般会配合back使用。

cpp 复制代码
  T& back() {return data_[size_-1];}
  const T& back() const {return data_[size_-1];} //const对象的情况要特别处理
  void pop_back() {
    if (size_ > 0) {
      data_(--size_).~T();
    }
  }

现在只剩下一些比较简单的方法还没实现:

cpp 复制代码
    T& operator[](size_t index) noexcept {return data_[index];}
    const T& operator[](size_t index) const noexcept {return data_[index];}
    size_t size() {return size_;}
    size_t capacity() {return capacity_;}
相关推荐
蜗牛互联网1 小时前
Python消费Responses SSE事件:增量文本、超时与取消
java·开发语言·人工智能·后端·python
郝学胜_神的一滴1 小时前
游戏引擎原理与实践 04:拆解引擎基础系统与内存管理
c++·游戏
All for pursuit.2 小时前
【设计-1】208.实现Trie (前缀树)
数据结构·c++·算法·leetcode
weixin_307779132 小时前
C++代码实现MATLAB中的crossval函数功能
开发语言·c++·算法·matlab
ttwuai2 小时前
Go后台管理系统开源项目:4个官方仓库怎么追溯和核验
开发语言·golang·开源
谢亮_vipxieliang2 小时前
Go Worker Pool 设计——从原理到生产级实现
开发语言·后端·golang
青少儿编程课堂2 小时前
背包问题(0/1 背包与完全背包)解题精讲——动态规划入门
c++·python·算法·bfs·信息学竞赛
倒头就睡的小比特2 小时前
C++多态
c++
Escalating_xu3 小时前
【C 语言】深入理解指针(5):sizeof、strlen、数组名语义与指针笔试题全解析
java·c语言·开发语言