Step 04

reserve 与扩容

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

把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 04),替换: uninitialized_relocate_n、recommend_capacity、reallocate、reserve、shrink_to_fit。参考答案在 参考答案页;编译问题见 常见问题。

Step 04 — reserve 与扩容

reserve(n) 的契约:

  • 保证之后至少还能在不重分配的情况下装到 n 个元素。
  • 不改变 size(),不构造新元素。所以 Vector<NoDefault> 可以 reserve(100)。
  • n <= capacity() 时什么都不做(迭代器、指针保持有效)。

几何增长(1 → 2 → 4 → 8 …)放在 emplace_back 里用 recommend_capacity,不要放在 reserve 里。v.reserve(5) 就申请 5 个槽,和 std::vector 一致。

搬迁策略(强异常安全)

旧缓冲区上的元素要搬到新缓冲区。规则:

  1. T 的移动构造是 noexcept(或 T 根本不能拷贝)→ 移动。
  2. 否则 → 拷贝。拷贝抛异常时源对象还在,旧 vector 完好。

如果移动可能抛、又只能移动(unique_ptr),抛了就只能给基本保证:新缓冲区的半成品要析构掉,旧缓冲区尽量别动。uninitialized_move_n 在抛之前不会 deallocate 旧内存,所以旧 vector 仍可用。

在作业纸里改哪里

搜索 TODO(step 04),替换:uninitialized_relocate_n、recommend_capacity、reallocate、reserve、shrink_to_fit。

可粘贴代码

template <typename T, typename Allocator>
void Vector<T, Allocator>::uninitialized_relocate_n(T* dest, T* src, size_type n) {
    if constexpr (std::is_nothrow_move_constructible_v<T> ||
                  !std::is_copy_constructible_v<T>) {
        uninitialized_move_n(dest, src, n);
    } else {
        uninitialized_copy_n(dest, src, n);
    }
}
template <typename T, typename Allocator>
typename Vector<T, Allocator>::size_type
Vector<T, Allocator>::recommend_capacity(size_type min_cap) const {
    const size_type max_n = max_size();
    if (min_cap > max_n) {
        throw std::length_error("mini::Vector::reserve");
    }
    size_type new_cap = 1;
    if (capacity_ > 0) {
        if (capacity_ > max_n / 2) {
            new_cap = max_n;
        } else {
            new_cap = capacity_ * 2;
        }
    }
    return new_cap < min_cap ? min_cap : new_cap;
}

capacity_ * 2 可能溢出,所以先和 max_n / 2 比较。

template <typename T, typename Allocator>
void Vector<T, Allocator>::reallocate(size_type new_cap) {
    T* new_data = allocate_n(new_cap);
    try {
        uninitialized_relocate_n(new_data, data_, size_);
    } catch (...) {
        deallocate_n(new_data, new_cap);
        throw;
    }
 
    T*        old_data = data_;
    size_type old_cap  = capacity_;
    size_type old_size = size_;
    data_              = new_data;
    capacity_          = new_cap;
    destroy_range(old_data, old_data + old_size);
    deallocate_n(old_data, old_cap);
}

顺序很重要:先在新内存上构造完毕,再析构旧元素。中途抛异常时旧缓冲区还活着。

template <typename T, typename Allocator>
void Vector<T, Allocator>::reserve(size_type new_cap) {
    if (new_cap <= capacity_) {
        return;
    }
    reallocate(new_cap);
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::shrink_to_fit() {
    if (size_ == capacity_) {
        return;
    }
    if (size_ == 0) {
        deallocate_n(data_, capacity_);
        data_     = nullptr;
        capacity_ = 0;
        return;
    }
    reallocate(size_);
}

验收

cmake --build build
./build/vector_tests --gtest_filter='Step04*'

ReserveKeepsExistingElements 用了 push_back,要 Step 05 一起绿。现在能过的是“空 vector reserve 之后 size 仍为 0、capacity 变大”这一类。

本步验收

cmake --build build
./build/vector_tests --gtest_filter='Step04*'