Step 03
构造与析构元素
把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 03),替换: destroy_range、uninitialized_value_construct_n、uninitialized_fill_n、uninitialized_copy_n、uninitialized_move_n、clear、pop_back、~Vector。参考答案在 参考答案页;编译问题见 常见问题。
Step 03 — 在未初始化内存上构造 / 析构元素
data_ + size_ 到 data_ + capacity_ 之间没有对象。你不能写 data_[size_] = value;,那是赋值给一个不存在的 T。必须:
AllocTraits::construct(alloc_, data_ + size_, /* 构造参数 */);
++size_;反过来,pop_back / clear / 析构函数必须先 destroy 再(可选)deallocate。只把 size_ 减掉而不析构,有非平凡析构函数的 T 会泄漏资源。
在作业纸里改哪里
搜索 TODO(step 03)。替换这 7 个类外定义:
| 函数 | 作用 |
|---|---|
destroy_range |
从尾到头析构 [first, last) |
uninitialized_value_construct_n |
值初始化 n 个(int 变成 0) |
uninitialized_fill_n |
用同一个 value 拷贝构造 n 个 |
uninitialized_copy_n |
从迭代器拷 n 个 |
uninitialized_move_n |
从另一块缓冲 move n 个 |
clear / pop_back / ~Vector |
对外接口 |
uninitialized_copy_n 是成员函数模板,作业纸里有两层 template,两层都要保留。
可粘贴代码
反向析构,后构造的元素先销毁。假定 T 的析构函数不抛异常(标准容器也这样假定)。
template <typename T, typename Allocator>
void Vector<T, Allocator>::destroy_range(T* first, T* last) noexcept {
while (last != first) {
--last;
AllocTraits::destroy(alloc_, last);
}
}部分构造成功、后面抛异常时:已经造好的必须析构,缓冲区由调用方归还,原来的 vector 保持不变。
template <typename T, typename Allocator>
void Vector<T, Allocator>::uninitialized_value_construct_n(T* dest, size_type n) {
size_type i = 0;
try {
for (; i < n; ++i) {
AllocTraits::construct(alloc_, dest + i); // 值初始化
}
} catch (...) {
destroy_range(dest, dest + i);
throw;
}
}template <typename T, typename Allocator>
void Vector<T, Allocator>::uninitialized_fill_n(T* dest, size_type n, const T& value) {
size_type i = 0;
try {
for (; i < n; ++i) {
AllocTraits::construct(alloc_, dest + i, value);
}
} catch (...) {
destroy_range(dest, dest + i);
throw;
}
}template <typename T, typename Allocator>
template <typename InputIt>
void Vector<T, Allocator>::uninitialized_copy_n(T* dest, InputIt src, size_type n) {
size_type i = 0;
try {
for (; i < n; ++i, ++src) {
AllocTraits::construct(alloc_, dest + i, *src);
}
} catch (...) {
destroy_range(dest, dest + i);
throw;
}
}template <typename T, typename Allocator>
void Vector<T, Allocator>::uninitialized_move_n(T* dest, T* src, size_type n) {
size_type i = 0;
try {
for (; i < n; ++i) {
AllocTraits::construct(alloc_, dest + i, std::move(src[i]));
}
} catch (...) {
destroy_range(dest, dest + i);
throw;
}
}construct(alloc_, p) 不带额外参数 → 值初始化。这就是 resize(n) 和 Vector(n) 对 int 得到 0 的原因。它仍然要求 T 可默认构造;没有默认构造的类型不要走这条路(Step 06)。
clear / pop_back / 析构函数
template <typename T, typename Allocator>
void Vector<T, Allocator>::clear() noexcept {
destroy_range(data_, data_ + size_);
size_ = 0;
// capacity_ 和 data_ 不动:这是 clear 和“换成空 vector”的区别
}template <typename T, typename Allocator>
void Vector<T, Allocator>::pop_back() {
AllocTraits::destroy(alloc_, data_ + size_ - 1);
--size_;
}template <typename T, typename Allocator>
Vector<T, Allocator>::~Vector() {
destroy_range(data_, data_ + size_);
deallocate_n(data_, capacity_);
}pop_back 在空 vector 上是未定义行为,和 std::vector 一样。不要在这里写 if (empty()) return; 当成“完整实现”——标准就是 UB。测试里不会对空 vector 调用它。
验收
cmake --build build
./build/vector_tests --gtest_filter='Step03*'DestructorDestroysEveryElement 依赖 emplace_back(Step 05)。可以先看代码,等 Step 05 再跑这条。
本步验收
cmake --build build
./build/vector_tests --gtest_filter='Step03*'