Step 07

拷贝、移动与交换

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

把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 07),替换: copy_from_range、move_from_range、reset_storage、steal_storage、swap_storage、拷贝构造(两个)、带分配器的移动构造、三个 operator=、swap。参考答案在 参考答案页;编译问题见 常见问题。

Step 07 — 拷贝、移动、swap

容器一拷贝就要 深拷贝元素,一移动就要 偷走缓冲区(O(1)),不要逐个 move 元素(那是 allocator 不相等时的退路)。

所以 Vector<std::unique_ptr<int>> 不可拷贝——这是对的,requires std::copy_constructible<T> 会把拷贝构造从重载集里拿掉。

作业纸里已经有的

默认移动构造已经写好,不要改:

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_)) {}

文件底部的 ADL swap 也已经写好。

在作业纸里改哪里

搜索 TODO(step 07)。替换:

  • copy_from_range / move_from_range
  • reset_storage / steal_storage / swap_storage
  • 拷贝构造(两个)
  • 带分配器的移动构造
  • 三个 operator=
  • swap

可粘贴代码

拷贝时 capacity 按 size 申请即可,不必复制对方多余的 capacity。新 vector 的分配器来自 select_on_container_copy_construction。

template <typename T, typename Allocator>
void Vector<T, Allocator>::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;
    }
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::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;
    }
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::reset_storage() noexcept {
    destroy_range(data_, data_ + size_);
    deallocate_n(data_, capacity_);
    data_     = nullptr;
    size_     = 0;
    capacity_ = 0;
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::steal_storage(Vector& other) noexcept {
    data_     = std::exchange(other.data_, nullptr);
    size_     = std::exchange(other.size_, 0);
    capacity_ = std::exchange(other.capacity_, 0);
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::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>
Vector<T, Allocator>::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_);
}
template <typename T, typename Allocator>
Vector<T, Allocator>::Vector(const Vector& other, const Allocator& alloc)
    requires std::copy_constructible<T>
    : alloc_(alloc) {
    copy_from_range(other.data_, other.size_);
}

带分配器的移动:若 alloc_ == other.alloc_ 仍然偷缓冲区;否则只能把元素逐个 move 到用新分配器申请的内存上(两个内存池不能混用指针)。

template <typename T, typename Allocator>
Vector<T, Allocator>::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_);
}

拷贝赋值用临时对象 + 交换存储,自然有强异常安全,也处理好自赋值:

template <typename T, typename Allocator>
Vector<T, Allocator>& Vector<T, Allocator>::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;
}
template <typename T, typename Allocator>
Vector<T, Allocator>& Vector<T, Allocator>::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;
}
template <typename T, typename Allocator>
Vector<T, Allocator>& Vector<T, Allocator>::operator=(std::initializer_list<T> init)
    requires std::copy_constructible<T>
{
    Vector tmp(init, alloc_);
    swap_storage(tmp);
    return *this;
}
template <typename T, typename Allocator>
void Vector<T, Allocator>::swap(Vector& other) noexcept {
    using std::swap;
    if constexpr (AllocTraits::propagate_on_container_swap::value) {
        swap(alloc_, other.alloc_);
    }
    swap_storage(other);
}

propagate_on_container_* 是分配器的策略开关。std::allocator 的 POCMA 是 true,POCCA / POCS 是 false。教学上把三条都写全,自定义分配器才换得动。

验收

cmake --build build
./build/vector_tests --gtest_filter='Step07*'
./build/vector_tests --gtest_filter='MoveOnly.MoveConstructsTheVector'

本步验收

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