Skip to content

Instantly share code, notes, and snippets.

@ZJUGuoShuai
Last active August 19, 2023 16:00
Show Gist options
  • Save ZJUGuoShuai/27a4282b59a186a5dbc706fbff492704 to your computer and use it in GitHub Desktop.
Save ZJUGuoShuai/27a4282b59a186a5dbc706fbff492704 to your computer and use it in GitHub Desktop.
My implementation of std::vector
template <typename T>
class vector {
size_t size_ = 0;
size_t capacity_ = 0;
T* data_ = nullptr;
public:
// Default constructor
vector() = default;
// Constructor with size
explicit vector(size_t size)
: size_(size), capacity_(size), data_(size_ ? new T[size_] : nullptr) {}
// Copy constructor
vector(const vector& v)
: size_(v.size_), capacity_(v.capacity_), data_(size_ ? new T[size_] : nullptr) {
std::copy(v.data_, v.data_ + size_, data_);
}
// Move constructor
vector(vector&& v) noexcept {
swap(*this, v);
}
// Destructor
~vector() {
delete[] data_;
}
// Access elements with boundary check
T& at(size_t index) const {
if (index < 0 || index >= size_) {
throw std::out_of_range("Index out of range");
}
return data_[index];
}
// Access elements without boundary check
T& operator[](size_t index) {
return data_[index];
}
void push_back(const T& value) {
if (size_ == capacity_) {
reserve(capacity_ == 0 ? 1 : capacity_ * 2);
}
data_[size_++] = value;
}
void pop_back() {
if (size_ > 0) {
size_--;
}
}
void reserve(size_t capacity) {
if (capacity > capacity_) {
T* newData = new T[capacity];
std::copy(data_, data_ + size_, newData);
delete[] data_;
data_ = newData;
capacity_ = capacity;
}
}
void resize(size_t size) {
reserve(size);
size_ = size;
}
size_t size() const {
return size_;
}
size_t capacity() const {
return capacity_;
}
vector& operator=(vector v) {
swap(*this, v);
return *this;
}
vector& operator=(vector&& v) noexcept {
swap(*this, v);
return *this;
}
//! IMPORTANT SWAP FUNCTION
friend void swap(vector& a, vector& b) {
// enable ADL (not necessary in our case, but good practice)
using std::swap;
swap(a.size_, b.size_);
swap(a.capacity_, b.capacity_);
swap(a.data_, b.data_);
}
};
@ZJUGuoShuai
Copy link
Author

vector<T> 类模板仅适用于 T 是基础内置类型,未对 Class 类型进行优化。

要点

  1. vector(size_t size) 构造函数声明为 explicit 的,能避免从 size_tvector隐式类型转换(这通常都是我们不希望发生的)。

  2. 通过 new 初始化 data_ 之前,先检查 size 是否为 0,如果为 0,应当把 data_ 设为 nullptr

    data_(size_ ? new T[size_] : nullptr)

    这是因为 new 允许我们申请大小为 0 的空间,而此时 new 仍然会返回一个地址,但这个地址没有意义。为保持 data_ 的语义(如果没数据,就应该是 nullptr),这里就在 size 为 0 的时候避免 new,直接设置 data_nullptr

  3. std::copy 而不是 C 标准库中的 memcpy,更 C++。

  4. 析构函数在 delete[] data_ 之前不需要检查 data_ 指针是否为空,因为即使 data_ 为空,也不会有问题。

    参见:c++ - Is it still safe to delete nullptr in c++0x? - Stack Overflow

  5. 在 Class 内使用 vector 这个名字等价于使用 vector<T>,这被称为 injected class name

    参见:c++ - using class name in a class template without template parameters - Stack Overflow

  6. const 修饰那些不会修改对象实例的成员函数,如 at()size()capacity(),才能让 const 对象调用这些方法。

  7. at() 会进行边界检查,而 operator[] 则不会。

  8. reserve() 来实现 resize(),可以实现代码复用。

  9. 定义 swap 友元函数,并用它来实现 Copy/Move Assignment Operator 和 Move Constructor,即 Copy-and-Swap Idiom,能避免 Self-Assignment 的问题,提供异常安全。

  10. noexcept 修饰 Move Constructor 和 Move Assignment Operator,能在某些时候带来性能优化,如 std::vector 会在扩容的时候,根据其元素 Class 的 Move 是否可能抛出异常来决定是否使用 Move,如果你指明你的 Class 的 Move 不会抛出异常,那 std::vector 就能大胆地在数据转移的时候使用 Move 而不需要进行拷贝了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment