Last active
August 19, 2023 16:00
-
-
Save ZJUGuoShuai/27a4282b59a186a5dbc706fbff492704 to your computer and use it in GitHub Desktop.
My implementation of std::vector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
本
vector<T>
类模板仅适用于T
是基础内置类型,未对 Class 类型进行优化。要点
将
vector(size_t size)
构造函数声明为explicit
的,能避免从size_t
到vector
的隐式类型转换(这通常都是我们不希望发生的)。通过
new
初始化data_
之前,先检查size
是否为 0,如果为 0,应当把data_
设为nullptr
。这是因为
new
允许我们申请大小为 0 的空间,而此时new
仍然会返回一个地址,但这个地址没有意义。为保持data_
的语义(如果没数据,就应该是nullptr
),这里就在size
为 0 的时候避免new
,直接设置data_
为nullptr
。用
std::copy
而不是 C 标准库中的memcpy
,更 C++。析构函数在
delete[] data_
之前不需要检查data_
指针是否为空,因为即使data_
为空,也不会有问题。参见:c++ - Is it still safe to delete nullptr in c++0x? - Stack Overflow
在 Class 内使用
vector
这个名字等价于使用vector<T>
,这被称为 injected class name。参见:c++ - using class name in a class template without template parameters - Stack Overflow
用
const
修饰那些不会修改对象实例的成员函数,如at()
、size()
和capacity()
,才能让 const 对象调用这些方法。at()
会进行边界检查,而operator[]
则不会。用
reserve()
来实现resize()
,可以实现代码复用。定义
swap
友元函数,并用它来实现 Copy/Move Assignment Operator 和 Move Constructor,即 Copy-and-Swap Idiom,能避免 Self-Assignment 的问题,提供异常安全。用
noexcept
修饰 Move Constructor 和 Move Assignment Operator,能在某些时候带来性能优化,如std::vector
会在扩容的时候,根据其元素 Class 的 Move 是否可能抛出异常来决定是否使用 Move,如果你指明你的 Class 的 Move 不会抛出异常,那std::vector
就能大胆地在数据转移的时候使用 Move 而不需要进行拷贝了。