Step 05
emplace_back / push_back
把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 05),替换: push_back(const T&)、push_back(T&&)、emplace_back。参考答案在 参考答案页;编译问题见 常见问题。
Step 05 — emplace_back / push_back
emplace_back(args...) 用 args 就地构造 一个 T,返回新元素的引用(C++17 起 std::vector 也这样)。
push_back 只是它的两个包装。
有空槽:直接 construct
if (size_ < capacity_) {
AllocTraits::construct(alloc_, data_ + size_, std::forward<Args>(args)...);
++size_;
return data_[size_ - 1];
}construct 抛了就不要 ++size_。这是强异常安全。
没空槽:先造新元素,再搬旧的
错误顺序:
- 分配新缓冲区
- 把旧元素 move 过去
destroy旧缓冲区- 再用
args构造新元素
如果 args 里有 v[0] 这种指向旧缓冲区的引用,第 3 步之后引用悬空。v.push_back(v[0]) 会炸。
正确顺序:
- 分配新缓冲区
- 在
new_data[size_]上用args构造新元素(此时旧缓冲区还在,引用有效) - 把旧的
size_个元素 relocate 到new_data[0 .. size_) - 析构并归还旧缓冲区
data_ / capacity_ / size_一起提交
在作业纸里改哪里
搜索 TODO(step 05),替换 push_back(const T&)、push_back(T&&)、emplace_back。
emplace_back 是成员函数模板:作业纸里已有 template <typename... Args> 那一层,两层 template 都要保留。
可粘贴代码
template <typename T, typename Allocator>
void Vector<T, Allocator>::push_back(const T& value)
requires std::copy_constructible<T>
{
emplace_back(value);
}template <typename T, typename Allocator>
void Vector<T, Allocator>::push_back(T&& value) {
emplace_back(std::move(value));
}template <typename T, typename Allocator>
template <typename... Args>
typename Vector<T, Allocator>::reference
Vector<T, Allocator>::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];
}uninitialized_relocate_n 自己会清掉搬迁到一半的元素。catch 里只要再拆掉那个已经造好的新元素,然后 deallocate 整块新内存。旧 vector 原封不动。
容量公式:recommend_capacity(size_ + 1),所以连续 push_back 是 1、2、4、8… 增长,均摊 O(1)。
如果编译器抱怨 push_back(const T&) 的 requires 和声明不一致:作业纸 class 里这一行必须是
void push_back(const T& value)
requires std::copy_constructible<T>;起步页提供的作业纸已经写好。不要删掉 requires。
验收
cmake --build build
./build/vector_tests --gtest_filter='Step05*'
./build/vector_tests --gtest_filter='MoveOnly.EmplaceAndPushRvalue'重点看 PushBackSelfReferenceIsSafe:它会在反复扩容的情况下 push_back(v[0])。
本步验收
cmake --build build
./build/vector_tests --gtest_filter='Step05*'