Answer key

完整参考实现

若你走的是「先看绿测试再拆」路线,把下面文件保存为 include/mini_vector/vector.hpp,CMake不要打开 MINI_VECTOR_USE_STUDENT。 正经抄写请继续改作业纸,这里只在卡住时对照。编译报错见 常见问题。

include/mini_vector/vector.hpp默认编译目标。抄完作业纸后也可以逐函数对照。
#pragma once

#include <concepts>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>

namespace mini {

// 手搓 std::vector 的教学实现。
// 跟着 tutorial/ 一步步抄:每段逻辑都标了 Step 编号。
template <typename T, typename Allocator = std::allocator<T>>
class Vector {
public:
    using value_type             = T;
    using allocator_type         = Allocator;
    using size_type              = std::size_t;
    using difference_type        = std::ptrdiff_t;
    using reference              = T&;
    using const_reference        = const T&;
    using pointer                = typename std::allocator_traits<Allocator>::pointer;
    using const_pointer          = typename std::allocator_traits<Allocator>::const_pointer;
    using iterator               = T*;
    using const_iterator         = const T*;
    using reverse_iterator       = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

private:
    using AllocTraits = std::allocator_traits<Allocator>;

    // Step 01: 三个核心状态。capacity_ 是已申请的槽位数,size_ 是已构造的元素数。
    T*        data_{nullptr};
    size_type size_{0};
    size_type capacity_{0};

    // Step 02: 空分配器不占额外空间(C++20)。
    [[no_unique_address]] Allocator alloc_{};

public:
    // ---------------------------------------------------------------------
    // 构造 / 析构
    // ---------------------------------------------------------------------

    Vector() noexcept(std::is_nothrow_default_constructible_v<Allocator>) = default;

    explicit Vector(const Allocator& alloc) noexcept
        : alloc_(alloc) {}

    // 需要 T 能被 allocator 无参 construct(默认构造 / 值初始化)。
    explicit Vector(size_type count, const Allocator& alloc = Allocator())
        requires std::default_initializable<T>
        : alloc_(alloc) {
        if (count == 0) {
            return;
        }
        data_     = allocate_n(count);
        capacity_ = count;
        try {
            uninitialized_value_construct_n(data_, count);
            size_ = count;
        } catch (...) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            throw;
        }
    }

    Vector(size_type count, const T& value, const Allocator& alloc = Allocator())
        requires std::copy_constructible<T>
        : alloc_(alloc) {
        if (count == 0) {
            return;
        }
        data_     = allocate_n(count);
        capacity_ = count;
        try {
            uninitialized_fill_n(data_, count, value);
            size_ = count;
        } catch (...) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            throw;
        }
    }

    Vector(std::initializer_list<T> init, const Allocator& alloc = Allocator())
        requires std::copy_constructible<T>
        : alloc_(alloc) {
        const size_type count = init.size();
        if (count == 0) {
            return;
        }
        data_     = allocate_n(count);
        capacity_ = count;
        try {
            uninitialized_copy_n(data_, init.begin(), count);
            size_ = count;
        } catch (...) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            throw;
        }
    }

    Vector(const Vector& other)
        requires std::copy_constructible<T>
        : alloc_(AllocTraits::select_on_container_copy_construction(other.alloc_)) {
        copy_from_range(other.data_, other.size_);
    }

    Vector(const Vector& other, const Allocator& alloc)
        requires std::copy_constructible<T>
        : alloc_(alloc) {
        copy_from_range(other.data_, other.size_);
    }

    Vector(Vector&& other) noexcept
        : data_(std::exchange(other.data_, nullptr)),
          size_(std::exchange(other.size_, 0)),
          capacity_(std::exchange(other.capacity_, 0)),
          alloc_(std::move(other.alloc_)) {}

    Vector(Vector&& other, const Allocator& alloc)
        : alloc_(alloc) {
        if (alloc_ == other.alloc_) {
            data_     = std::exchange(other.data_, nullptr);
            size_     = std::exchange(other.size_, 0);
            capacity_ = std::exchange(other.capacity_, 0);
            return;
        }
        move_from_range(other.data_, other.size_);
    }

    ~Vector() {
        destroy_range(data_, data_ + size_);
        deallocate_n(data_, capacity_);
    }

    Vector& operator=(const Vector& other)
        requires std::copy_constructible<T>
    {
        if (this == &other) {
            return *this;
        }
        if constexpr (AllocTraits::propagate_on_container_copy_assignment::value) {
            Vector tmp(other, other.alloc_);
            alloc_ = other.alloc_;
            swap_storage(tmp);
        } else {
            Vector tmp(other, alloc_);
            swap_storage(tmp);
        }
        return *this;
    }

    Vector& operator=(Vector&& other) noexcept(
        AllocTraits::propagate_on_container_move_assignment::value ||
        AllocTraits::is_always_equal::value) {
        if (this == &other) {
            return *this;
        }

        if constexpr (AllocTraits::propagate_on_container_move_assignment::value) {
            reset_storage();
            steal_storage(other);
            alloc_ = std::move(other.alloc_);
        } else if (alloc_ == other.alloc_) {
            reset_storage();
            steal_storage(other);
        } else {
            Vector tmp(std::move(other), alloc_);
            swap_storage(tmp);
        }
        return *this;
    }

    Vector& operator=(std::initializer_list<T> init)
        requires std::copy_constructible<T>
    {
        Vector tmp(init, alloc_);
        swap_storage(tmp);
        return *this;
    }

    allocator_type get_allocator() const noexcept {
        return alloc_;
    }

    // ---------------------------------------------------------------------
    // 元素访问
    // ---------------------------------------------------------------------

    reference at(size_type index) {
        if (index >= size_) {
            throw std::out_of_range("mini::Vector::at");
        }
        return data_[index];
    }

    const_reference at(size_type index) const {
        if (index >= size_) {
            throw std::out_of_range("mini::Vector::at");
        }
        return data_[index];
    }

    reference operator[](size_type index) noexcept {
        return data_[index];
    }

    const_reference operator[](size_type index) const noexcept {
        return data_[index];
    }

    reference front() noexcept {
        return data_[0];
    }

    const_reference front() const noexcept {
        return data_[0];
    }

    reference back() noexcept {
        return data_[size_ - 1];
    }

    const_reference back() const noexcept {
        return data_[size_ - 1];
    }

    T* data() noexcept {
        return data_;
    }

    const T* data() const noexcept {
        return data_;
    }

    // ---------------------------------------------------------------------
    // 迭代器
    // ---------------------------------------------------------------------

    iterator begin() noexcept {
        return data_;
    }

    const_iterator begin() const noexcept {
        return data_;
    }

    const_iterator cbegin() const noexcept {
        return data_;
    }

    iterator end() noexcept {
        return data_ + size_;
    }

    const_iterator end() const noexcept {
        return data_ + size_;
    }

    const_iterator cend() const noexcept {
        return data_ + size_;
    }

    reverse_iterator rbegin() noexcept {
        return reverse_iterator(end());
    }

    const_reverse_iterator rbegin() const noexcept {
        return const_reverse_iterator(end());
    }

    reverse_iterator rend() noexcept {
        return reverse_iterator(begin());
    }

    const_reverse_iterator rend() const noexcept {
        return const_reverse_iterator(begin());
    }

    // ---------------------------------------------------------------------
    // 容量
    // ---------------------------------------------------------------------

    [[nodiscard]] bool empty() const noexcept {
        return size_ == 0;
    }

    size_type size() const noexcept {
        return size_;
    }

    size_type max_size() const noexcept {
        const auto alloc_max = AllocTraits::max_size(alloc_);
        const auto diff_max =
            static_cast<size_type>(std::numeric_limits<difference_type>::max());
        return alloc_max < diff_max ? alloc_max : diff_max;
    }

    size_type capacity() const noexcept {
        return capacity_;
    }

    // Step 04: 只申请内存,不构造新元素。n <= capacity 时是空操作。
    void reserve(size_type new_cap) {
        if (new_cap <= capacity_) {
            return;
        }
        reallocate(new_cap);
    }

    void shrink_to_fit() {
        if (size_ == capacity_) {
            return;
        }
        if (size_ == 0) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            return;
        }
        reallocate(size_);
    }

    // ---------------------------------------------------------------------
    // 修改器
    // ---------------------------------------------------------------------

    void clear() noexcept {
        destroy_range(data_, data_ + size_);
        size_ = 0;
    }

    void push_back(const T& value)
        requires std::copy_constructible<T>
    {
        emplace_back(value);
    }

    void push_back(T&& value) {
        emplace_back(std::move(value));
    }

    // Step 05: 就地构造。扩容时先在新缓冲区构造新元素,再搬迁旧元素,
    // 这样 push_back(v[0]) 这种“引用指向自己”的写法也是安全的。
    template <typename... Args>
    reference emplace_back(Args&&... args) {
        if (size_ < capacity_) {
            AllocTraits::construct(alloc_, data_ + size_, std::forward<Args>(args)...);
            ++size_;
            return data_[size_ - 1];
        }

        const size_type new_cap  = recommend_capacity(size_ + 1);
        T*              new_data = allocate_n(new_cap);
        T*              new_elem = nullptr;
        try {
            AllocTraits::construct(
                alloc_, new_data + size_, std::forward<Args>(args)...);
            new_elem = new_data + size_;
            uninitialized_relocate_n(new_data, data_, size_);
        } catch (...) {
            if (new_elem != nullptr) {
                AllocTraits::destroy(alloc_, new_elem);
            }
            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;
        ++size_;
        destroy_range(old_data, old_data + old_size);
        deallocate_n(old_data, old_cap);
        return data_[size_ - 1];
    }

    void pop_back() {
        AllocTraits::destroy(alloc_, data_ + size_ - 1);
        --size_;
    }

    // Step 06: 无参 resize 需要 T 可默认构造;带值的 overload 不需要。
    void resize(size_type count)
        requires std::default_initializable<T>
    {
        if (count < size_) {
            destroy_range(data_ + count, data_ + size_);
            size_ = count;
            return;
        }
        if (count == size_) {
            return;
        }
        if (count <= capacity_) {
            uninitialized_value_construct_n(data_ + size_, count - size_);
            size_ = count;
            return;
        }
        grow_buffer_and_append(count, [this](T* dest, size_type n) {
            uninitialized_value_construct_n(dest, n);
        });
    }

    void resize(size_type count, const T& value)
        requires std::copy_constructible<T>
    {
        if (count < size_) {
            destroy_range(data_ + count, data_ + size_);
            size_ = count;
            return;
        }
        if (count == size_) {
            return;
        }
        // 先拷一份,避免 value 指向本 vector 内元素时重分配使引用失效。
        T extra(value);
        if (count <= capacity_) {
            uninitialized_fill_n(data_ + size_, count - size_, extra);
            size_ = count;
            return;
        }
        grow_buffer_and_append(count, [this, &extra](T* dest, size_type n) {
            uninitialized_fill_n(dest, n, extra);
        });
    }

    void swap(Vector& other) noexcept {
        using std::swap;
        if constexpr (AllocTraits::propagate_on_container_swap::value) {
            swap(alloc_, other.alloc_);
        }
        swap_storage(other);
    }

    friend bool operator==(const Vector& lhs, const Vector& rhs) {
        if (lhs.size_ != rhs.size_) {
            return false;
        }
        for (size_type i = 0; i < lhs.size_; ++i) {
            if (!(lhs.data_[i] == rhs.data_[i])) {
                return false;
            }
        }
        return true;
    }

    friend bool operator!=(const Vector& lhs, const Vector& rhs) {
        return !(lhs == rhs);
    }

private:
    // Step 02 -------------------------------------------------------------

    T* allocate_n(size_type n) {
        if (n == 0) {
            return nullptr;
        }
        return std::to_address(AllocTraits::allocate(alloc_, n));
    }

    void deallocate_n(T* ptr, size_type n) noexcept {
        if (ptr == nullptr) {
            return;
        }
        AllocTraits::deallocate(alloc_, ptr, n);
    }

    // Step 03 -------------------------------------------------------------

    void destroy_range(T* first, T* last) noexcept {
        while (last != first) {
            --last;
            AllocTraits::destroy(alloc_, last);
        }
    }

    void 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;
        }
    }

    void 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 InputIt>
    void 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;
        }
    }

    void 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;
        }
    }

    // 能无异常移动就移动;否则若可拷贝就拷贝,给 push_back / reserve 强异常安全。
    void 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);
        }
    }

    // Step 04 -------------------------------------------------------------

    size_type 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;
    }

    void 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);
    }

    // 扩容并在尾巴上构造新元素。失败时旧缓冲区原封不动(含 capacity)。
    template <typename TailCtor>
    void grow_buffer_and_append(size_type count, TailCtor&& construct_tail) {
        const size_type new_cap  = recommend_capacity(count);
        const size_type appended = count - size_;
        T*              new_data = allocate_n(new_cap);
        bool            tail_ok  = false;
        try {
            construct_tail(new_data + size_, appended);
            tail_ok = true;
            uninitialized_relocate_n(new_data, data_, size_);
        } catch (...) {
            if (tail_ok) {
                destroy_range(new_data + size_, new_data + count);
            }
            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;
        size_              = count;
        destroy_range(old_data, old_data + old_size);
        deallocate_n(old_data, old_cap);
    }

    void copy_from_range(const T* src, size_type n) {
        if (n == 0) {
            return;
        }
        data_     = allocate_n(n);
        capacity_ = n;
        try {
            uninitialized_copy_n(data_, src, n);
            size_ = n;
        } catch (...) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            throw;
        }
    }

    void move_from_range(T* src, size_type n) {
        if (n == 0) {
            return;
        }
        data_     = allocate_n(n);
        capacity_ = n;
        try {
            uninitialized_move_n(data_, src, n);
            size_ = n;
        } catch (...) {
            deallocate_n(data_, capacity_);
            data_     = nullptr;
            capacity_ = 0;
            throw;
        }
    }

    void reset_storage() noexcept {
        destroy_range(data_, data_ + size_);
        deallocate_n(data_, capacity_);
        data_     = nullptr;
        size_     = 0;
        capacity_ = 0;
    }

    void steal_storage(Vector& other) noexcept {
        data_     = std::exchange(other.data_, nullptr);
        size_     = std::exchange(other.size_, 0);
        capacity_ = std::exchange(other.capacity_, 0);
    }

    void swap_storage(Vector& other) noexcept {
        using std::swap;
        swap(data_, other.data_);
        swap(size_, other.size_);
        swap(capacity_, other.capacity_);
    }
};

template <typename T, typename Allocator>
void swap(Vector<T, Allocator>& lhs, Vector<T, Allocator>& rhs) noexcept {
    lhs.swap(rhs);
}

}  // namespace mini

用参考实现跑全套测试

cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++
cmake --build build
ctest --test-dir build --output-on-failure
./build/mini_vector_demo