Step 03

构造与析构元素

粘贴到作业纸 student/mini_vector/vector.hpp 底部不需要仓库,起步页已有全部工程文件

把本页「可粘贴代码」整段(从 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*'