Step 07
拷贝、移动与交换
把本页「可粘贴代码」整段(从 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_rangereset_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*'