Step 06
resize 与无默认构造
把本页「可粘贴代码」整段(从 template 到函数结尾)替换作业纸里对应的 TODO。不要贴进 class 体内。本步搜索 TODO(step 06) 和 TODO(step 01/06),替换: grow_buffer_and_append、resize(count)、resize(count, value)、Vector(count)、Vector(count, value)、Vector(initializer_list)。参考答案在 参考答案页;编译问题见 常见问题。
Step 06 — resize 与没有默认构造函数的 T
std::vector 有两个 resize:
void resize(size_type count); // 多出来的元素值初始化
void resize(size_type count, const T& value); // 多出来的元素拷贝 value前者要求 T DefaultInsertable(对 std::allocator 来说就是能 T())。
struct Widget { Widget(int); }; 没有默认构造,v.resize(10) 必须无法编译,但 v.resize(10, Widget{1}) 可以。
C++20 用 requires 把无参 overload 从重载集里拿掉。对 Vector<NoDefault>,v.resize(3) 会直接找不到函数,而不是实例化到一半报一串内部错误。测试里有对应的 static_assert(!requires { ... })。
同样的约束加在 Vector(size_type count) 上。Vector(size_type count, const T& value) 不要加 default_initializable,它只需要可拷贝。
缩小
只析构尾巴,capacity 不变:
if (count < size_) {
destroy_range(data_ + count, data_ + size_);
size_ = count;
return;
}为了让抛异常时 capacity 也不变,需要重分配时走独立缓冲区:新元素先造好,再搬旧元素,最后才切换指针。容量足够时直接在尾巴上 construct 即可。
在作业纸里改哪里
搜索 TODO(step 06) 以及 TODO(step 01/06)。替换:
grow_buffer_and_appendresize(count)resize(count, value)Vector(count)Vector(count, value)Vector(initializer_list)
grow_buffer_and_append 是成员函数模板,保留两层 template。
可粘贴代码
template <typename T, typename Allocator>
template <typename TailCtor>
void Vector<T, Allocator>::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);
}template <typename T, typename Allocator>
void Vector<T, Allocator>::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);
});
}带 value 的路径要小心别名:v.resize(100, v[0]) 里 value 引用的是 vector 自己的元素。重分配会让这个引用悬空,所以先拷到栈上。
template <typename T, typename Allocator>
void Vector<T, Allocator>::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;
}
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);
});
}三个构造函数
类外定义不要再写 = Allocator() 这种默认实参,默认实参只出现在 class 里的声明上。
template <typename T, typename Allocator>
Vector<T, Allocator>::Vector(size_type count, const Allocator& alloc)
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;
}
}template <typename T, typename Allocator>
Vector<T, Allocator>::Vector(size_type count, const T& value, const Allocator& alloc)
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;
}
}template <typename T, typename Allocator>
Vector<T, Allocator>::Vector(std::initializer_list<T> init, const Allocator& alloc)
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;
}
}带 value 的路径只要求 T 可拷贝构造,不要求默认构造。所以 Vector<NoDefault>(3, NoDefault{1}) 合法,Vector<NoDefault>(3) 非法。
验收
cmake --build build
./build/vector_tests --gtest_filter='Step06*'
./build/vector_tests --gtest_filter='NoDefaultCtor*'
./build/vector_tests --gtest_filter='Step01*'本步验收
cmake --build build
./build/vector_tests --gtest_filter='Step06*:NoDefaultCtor*'