本章目标:亲手实现 vector、string 的核心功能、 unique_ptr、以及一个简单的迭代器。这是检验"是否真懂 C++"的终极练习——理解标准库背后的一切。
注意:生产代码**永远用标准库**——你写的不会更好, 只会更慢/更多 bug。手写是学习手段,不是替代品。
核心设计(和真实 vector 一致):
骨架:
template <typename T>
class MyVector {
public:
MyVector() = default;
MyVector(const MyVector& o) { copy_from(o); }
MyVector(MyVector&& o) noexcept
: begin_(o.begin_), end_(o.end_), capacity_(o.capacity_) {
o.begin_ = o.end_ = o.capacity_ = nullptr;
}
MyVector& operator=(const MyVector& o) {
if (this != &o) { clear(); copy_from(o); }
return *this;
}
MyVector& operator=(MyVector&& o) noexcept {
if (this != &o) {
clear(); // 释放旧资源
begin_ = o.begin_; end_ = o.end_; capacity_ = o.capacity_;
o.begin_ = o.end_ = o.capacity_ = nullptr;
}
return *this;
}
~MyVector() { std::destroy(begin_, end_); delete[] reinterpret_cast<char*>(begin_); }
void push_back(const T& v) {
if (end_ == capacity_) grow();
new (end_) T(v); // 定位 new:就地构造
++end_;
}
T& operator[](std::size_t i) { return begin_[i]; }
std::size_t size() const { return end_ - begin_; }
std::size_t capacity() const { return capacity_ - begin_; }
T* begin() { return begin_; } // 迭代器 = 裸指针
T* end() { return end_; }
private:
void grow() {
std::size_t old_size = size(); // 先记旧 size(关键!)
std::size_t new_cap = capacity() == 0 ? 4 : capacity() * 2;
T* new_begin = reinterpret_cast<T*>(new char[new_cap * sizeof(T)]);
for (std::size_t i = 0; i < old_size; ++i)
new (new_begin + i) T(std::move(begin_[i])); // 移动元素
std::destroy(begin_, end_); // 析构旧元素
delete[] reinterpret_cast<char*>(begin_); // 释放旧内存
begin_ = new_begin;
end_ = new_begin + old_size;
capacity_ = new_begin + new_cap;
}
void copy_from(const MyVector& o) {
begin_ = reinterpret_cast<T*>(new char[o.size() * sizeof(T)]);
for (std::size_t i = 0; i < o.size(); ++i)
new (begin_ + i) T(o.begin_[i]);
end_ = begin_ + o.size();
capacity_ = end_;
}
void clear() {
std::destroy(begin_, end_);
delete[] reinterpret_cast<char*>(begin_);
begin_ = end_ = capacity_ = nullptr;
}
T* begin_ = nullptr;
T* end_ = nullptr;
T* capacity_ = nullptr;
};
关键知识点:
template <typename T>
class MyUniquePtr {
public:
MyUniquePtr() = default;
explicit MyUniquePtr(T* p) : ptr_(p) {}
~MyUniquePtr() { delete ptr_; }
MyUniquePtr(const MyUniquePtr&) = delete; // 禁止拷贝
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
MyUniquePtr(MyUniquePtr&& o) noexcept : ptr_(o.ptr_) {
o.ptr_ = nullptr; // 转移所有权
}
MyUniquePtr& operator=(MyUniquePtr&& o) noexcept {
if (this != &o) {
delete ptr_; // 释放旧资源
ptr_ = o.ptr_;
o.ptr_ = nullptr;
}
return *this;
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
void reset(T* p = nullptr) { delete ptr_; ptr_ = p; }
private:
T* ptr_ = nullptr;
};
要点:
unique_ptr<T[]> 特化。了解即可。
string 的两个灵魂:SSO(短字符串优化)+ 按需堆分配。 简化版(无 SSO):
class MyString {
char* data_ = nullptr;
std::size_t size_ = 0;
public:
MyString() = default;
MyString(const char* s) { assign(s); }
MyString(const MyString& o) { assign(o.c_str()); }
~MyString() { delete[] data_; }
// 移动(noexcept!)
MyString(MyString&& o) noexcept
: data_(o.data_), size_(o.size_) {
o.data_ = nullptr; o.size_ = 0;
}
void assign(const char* s) {
delete[] data_;
size_ = std::strlen(s);
data_ = new char[size_ + 1];
std::memcpy(data_, s, size_ + 1);
}
const char* c_str() const { return data_ ? data_ : ""; }
std::size_t size() const { return size_; }
char operator[](std::size_t i) const { return data_[i]; }
};
真实 std::string 在栈上放一个 16 字节的小缓冲区, 短字符串直接放里面(SSO),长字符串才堆分配。 这就是为什么短字符串操作那么快。
只要提供 begin()/end() 返回"支持 ++ * !="的类型, 就能用范围 for 和大部分 STL 算法:
// MyVector 的迭代器就是 T*(begin/end 已实现)
MyVector<int> v;
for (int i = 0; i < 10; ++i) v.push_back(i * i);
for (int x : v) std::println("{}", x); // 直接可用!
std::ranges::sort(v); // 算法也能用!
这就是"迭代器协议":STL 算法只要求迭代器满足特定操作, 不要求类型相同。鸭子类型思想在 C++ 的实现方式。
测试文件会验证:
迭代器遍历、和 std::ranges::sort 配合
练习题(配套测试:测试_第26章_手写容器.cpp)