教程:XEngine::utl::vector 类详解
这份教程将深入探讨 XEngine::utl 命名空间下的 vector 类模板的实现细节和使用方法。这个自定义的 vector 类旨在提供一个轻量级、可定制的动态数组容器,尤其关注性能和对内存管理的控制。
1. 概述
XEngine::utl::vector 是一个模板类,它模拟了标准库中的 std::vector,提供动态数组的功能。这意味着它可以根据需要自动调整大小。与 std::vector 相比,这个自定义版本可能更注重某些特定的性能考量或者提供了更底层的内存管理选项。
模板参数:
typename T: 指定vector中存储的元素类型。可以是任何 C++ 数据类型,包括内置类型和自定义类型。bool destruct = true: 一个布尔值模板参数,控制在容器销毁或元素移除时是否调用元素的析构函数。默认为true,表示会执行析构。如果设置为false,则不会调用析构函数,这在某些性能敏感的场景下可能有用,但需要谨慎使用,以避免资源泄漏。
2. 构造函数
vector 类提供了多种构造函数,以支持不同的初始化方式:
默认构造函数
vector() = default;:这是一个默认构造函数,使用
= default显式声明,表示使用编译器提供的默认实现。特点: 不会分配任何内存。初始状态下,
vector是空的,capacity和size均为 0,_data指针为nullptr。用法:
C++
XEngine::utl::vector<int> vec1; // 创建一个空的 vector计数构造函数
constexpr explicit vector(u64 count):使用
explicit关键字,防止隐式类型转换。constexpr表明此构造函数在编译时也可能执行。参数:
count(u64类型) - 指定vector的初始大小。功能: 调用
resize(count)来分配能容纳count个元素的内存,并使用默认构造函数初始化这些元素。用法:
C++
XEngine::utl::vector<int> vec2(10); // 创建一个包含 10 个默认初始化 int 元素的 vector计数和值构造函数
constexpr explicit vector(u64 count, const T& value):同样使用
explicit和constexpr。参数:
count(u64类型) - 指定vector的初始大小。value(const T&类型) - 用于初始化所有元素的常量引用值。
功能: 调用
resize(count, value)分配内存并使用value的拷贝初始化所有元素。用法:
C++
XEngine::utl::vector<float> vec3(5, 3.14f); // 创建一个包含 5 个 float 元素,均初始化为 3.14 的 vector拷贝构造函数
constexpr vector(const vector& o):参数:
o(const vector&类型) - 要拷贝的vector对象的常量引用。功能: 执行深拷贝。它会分配新的内存,并将源
vectoro中的所有元素拷贝到新的vector中。用法:
C++
XEngine::utl::vector<int> vec4 = vec2; // 使用拷贝构造函数,vec4 是 vec2 的一个副本移动构造函数
constexpr vector(const vector&& o):参数:
o(const vector&&类型) - 要移动的vector对象的右值引用。功能: 执行浅拷贝(移动语义)。它会将源
vectoro的内部指针(_capacity,_size,_data)移动到新的vector,而不会复制元素。之后,源vectoro会被置于有效但未指定的状态 (通过o.reset()实现)。这避免了深拷贝的开销,提高了性能,尤其是在处理大型vector时。用法:
C++
XEngine::utl::vector<int> vec5 = std::move(vec2); // 使用移动构造函数,vec5 接管 vec2 的资源,vec2 变为 empty迭代器范围构造函数
template<typename it, typename = std::enable_if_t<std::_Is_iterator_v<it>>> constexpr explicit vector(it first, it last):模板参数:
it- 迭代器类型。约束: 使用
std::enable_if_t和std::_Is_iterator_v确保it必须是一个迭代器类型。参数:
first(迭代器) - 范围的起始迭代器。last(迭代器) - 范围的结束迭代器(不包含)。
功能: 通过迭代器范围
[first, last)初始化vector。它会遍历迭代器范围内的元素,并使用emplace_back将每个元素添加到vector中。用法:
C++
int arr[] = {1, 2, 3, 4, 5}; XEngine::utl::vector<int> vec6(std::begin(arr), std::end(arr)); // 从数组 arr 初始化 vector
3. 析构函数和资源管理
析构函数
~vector() { destroy(); }:负责在
vector对象生命周期结束时释放其占用的资源。它调用
destroy()成员函数来完成实际的清理工作。
destroy()成员函数 (private):功能:
断言检查:
assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }());这是一个复杂的断言,用于检查内部状态的一致性。它确保如果_capacity大于 0 (即分配了内存),则_data指针不能为nullptr;反之,如果_capacity为 0,则_data必须为nullptr。这有助于在开发阶段尽早发现内存管理错误。清理元素: 调用
clear()函数来析构vector中所有已构造的元素(如果destruct模板参数为true)。释放内存:
_capacity = 0;和_size = 0;重置容量和大小。if (_data) free(_data);如果_data指针不为空 (即分配了内存),则使用free()函数释放之前通过realloc()或其他方式分配的内存。_data = nullptr;将_data指针置为nullptr,防止悬挂指针。
reset()成员函数 (private):功能: 将
vector重置为空状态,但不释放已分配的内存。主要用于移动操作后清理源对象。操作: 将
_capacity和_size设置为 0,并将_data指针设置为nullptr。
destruct_range(u64 first, u64 last)成员函数 (private):功能: 析构
vector中指定范围[first, last)内的元素。条件编译:
if constexpr (destruct)只有当vector模板参数destruct为true时,才会执行元素的析构。断言:
assert(destruct); assert(first <= _size && last <= _size && first <= last);确保destruct为true,且范围索引有效。析构循环: 如果
_data指针有效,则循环遍历索引从first到last的元素,并对每个元素调用析构函数_data[first].~T();。
4. 赋值运算符
拷贝赋值运算符
constexpr vector& operator=(const vector& o):参数:
o(const vector&类型) - 要赋值的源vector对象的常量引用。功能: 实现深拷贝赋值。
自赋值检查:
assert(this != std::addressof(o)); if (this != std::addressof(o))防止自赋值,如果尝试vec = vec;,则直接返回*this。清理现有资源:
clear();首先清理当前vector对象已有的元素 (并根据destruct模板参数决定是否析构)。预留空间:
reserve(o._size);为新的元素预留足够的容量,大小与源vectoro相同,以减少后续emplace_back操作中的内存重新分配。元素拷贝:
for (auto& item : o) { emplace_back(item); }遍历源vectoro的每个元素,并使用emplace_back将其拷贝到当前vector中。emplace_back(item)会调用类型T的拷贝构造函数。大小断言:
assert(_size == o._size);赋值完成后,断言当前vector的大小与源vector的大小相同,确保拷贝正确完成。
返回值:
return *this;返回当前vector对象的引用,支持链式赋值操作 (例如vec1 = vec2 = vec3;)。
移动赋值运算符
constexpr vector& operator=(vector&& o):参数:
o(vector&&类型) - 要赋值的源vector对象的右值引用。功能: 实现移动赋值。
自赋值检查:
assert(this != std::addressof(o)); if (this != std::addressof(o))防止自赋值。销毁现有资源:
destroy();销毁当前vector对象已有的资源 (包括已分配的内存和元素)。资源移动:
move(o);调用私有成员函数move(o)将源vectoro的内部指针 (_capacity,_size,_data) 移动到当前vector对象。
返回值:
return *this;返回当前vector对象的引用,支持链式赋值。
5. 容量操作
reserve(u64 new_capacity):参数:
new_capacity(u64类型) - 新的容量大小。功能: 预留至少能容纳
new_capacity个元素的内存空间。容量检查:
if (new_capacity > _capacity)只有当new_capacity大于当前容量_capacity时,才需要重新分配内存。内存重分配:
void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) };使用realloc()函数尝试重新分配内存。realloc()的优点是,如果可能,它会在原有内存块的基础上扩展内存,如果无法扩展,则会分配新的内存块,并将原有数据拷贝到新的内存块中。断言检查:
assert(new_buffer);断言内存分配成功。如果realloc()失败返回nullptr,则程序会终止。更新内部状态:
if (new_buffer)如果内存分配成功 (new_buffer不为nullptr):_data = static_cast<T*>(new_buffer);更新_data指针指向新的内存块。_capacity = new_capacity;更新_capacity为新的容量值。
resize(u64 new_size):参数:
new_size(u64类型) - 新的大小。功能: 改变
vector的大小为new_size。静态断言:
static_assert(std::is_default_constructible_v<T>, "Type must be default-constructible.");要求元素类型T必须是默认可构造的 (即有默认构造函数)。因为如果new_size大于当前_size,需要创建新的元素。扩容:
if (new_size > _size)reserve(new_size);首先预留至少new_size的容量。while (_size < new_size) { emplace_back(); }循环调用emplace_back()添加新的元素,直到_size达到new_size。由于emplace_back()内部会使用默认构造函数T()创建新元素,因此要求T必须是默认可构造的。
缩容:
else if (new_size < _size)if constexpr (destruct) { destruct_range(new_size, _size); }如果new_size小于当前_size,则需要移除多余的元素。如果destruct模板参数为true,则调用destruct_range()析构索引从new_size到_size范围内的元素。
大小断言:
assert(new_size == _size);操作完成后,断言vector的大小确实变为new_size。
resize(u64 new_size, const T& value):参数:
new_size(u64类型) - 新的大小。value(const T&类型) - 用于新元素的拷贝值。
功能: 改变
vector的大小为new_size,并可以指定新元素的初始值。静态断言:
static_assert(std::is_copy_constructible_v<T>, "Type must be copy-constructible.");要求元素类型T必须是拷贝可构造的 (即有拷贝构造函数),因为新元素需要用value的拷贝来初始化。扩容:
if (new_size > _size)reserve(new_size);预留容量。while (_size < new_size) { emplace_back(value); }循环调用emplace_back(value)添加新元素,并使用value的拷贝进行初始化。
缩容:
else if (new_size < _size)if constexpr (destruct) { destruct_range(new_size, _size); }如果new_size小于当前_size,则移除多余的元素,并根据destruct模板参数决定是否析构。
大小断言:
assert(new_size == _size);操作完成后,断言大小为new_size。
capacity() const:返回值:
u64- 返回当前vector的容量,即已分配的内存空间可以容纳的元素数量。
size() const:返回值:
u64- 返回当前vector中实际存储的元素数量。
empty() const:返回值:
bool- 如果vector为空 (size为 0),则返回true,否则返回false。
6. 元素访问
operator[](u64 index)(非const和const版本):参数:
index(u64类型) - 要访问的元素的索引。功能: 提供对
vector中元素的直接访问,类似于数组访问。断言:
assert(_data && index < _size);在访问前进行断言检查,确保_data指针有效且索引index在有效范围内[0, _size)。如果断言失败,程序会终止,这有助于调试越界访问错误。返回值:
非
const版本:T&- 返回索引index处元素的引用 (左值引用),允许修改元素的值。const版本:const T&- 返回索引index处元素的常量引用 (常量左值引用),只允许读取元素的值,不能修改。
用法:
C++
XEngine::utl::vector<int> vec = {10, 20, 30}; vec[0] = 15; // 使用非 const operator[] 修改元素 int val = vec[1]; // 使用 const operator[] 读取元素front()(非const和const版本):功能: 访问
vector的第一个元素。断言:
assert(_data && _size);断言_data指针有效且vector不为空 (_size > 0)。返回值:
非
const版本:T&- 返回第一个元素的引用 (左值引用)。const版本:const T&- 返回第一个元素的常量引用 (常量左值引用)。
用法:
C++
int first_element = vec.front(); // 获取第一个元素back()(非const和const版本):功能: 访问
vector的最后一个元素。断言:
assert(_data && _size);断言_data指针有效且vector不为空。返回值:
非
const版本:T&- 返回最后一个元素的引用 (左值引用)。const版本:const T&- 返回最后一个元素的常量引用 (常量左值引用)。
用法:
C++
int last_element = vec.back(); // 获取最后一个元素data()(非const和const版本):功能: 获取指向
vector内部原始数据数组的指针。返回值:
非
const版本:T*- 返回指向第一个元素的非const指针,允许通过指针修改元素。const版本:T *const- 返回指向第一个元素的const指针,只允许通过指针读取元素,不能修改。
用法:
C++
int* data_ptr = vec.data(); // 获取指向 vector 数据的指针
7. 迭代器
begin()(非const和const版本):功能: 返回指向
vector起始位置的迭代器。断言:
assert(_data);断言_data指针有效。返回值:
非
const版本:T*- 返回指向第一个元素的指针,作为非const迭代器。const版本:const T*- 返回指向第一个元素的const指针,作为const迭代器。
end()(非const和const版本):功能: 返回指向
vector结束位置的迭代器,即最后一个元素之后的位置。断言:
assert(_data);断言_data指针有效。返回值:
非
const版本:T*- 返回指向_data[_size]的指针,作为非const迭代器。const版本:const T*- 返回指向_data[_size]的const指针,作为const迭代器。
用法:
C++
for (auto it = vec.begin(); it != vec.end(); ++it) { // 使用迭代器遍历 vector std::cout << *it << " "; } for (const auto& element : vec) { // 范围 for 循环,底层使用迭代器 std::cout << element << " "; }
8. 修改器
push_back(const T& value):参数:
value(const T&类型) - 要添加到vector末尾的元素的常量引用。功能: 在
vector的末尾添加一个元素,元素的值是value的拷贝。实现: 直接调用
emplace_back(value)完成添加操作。
push_back(const T&& value):参数:
value(const T&&类型) - 要添加到vector末尾的元素的右值引用。功能: 在
vector的末尾添加一个元素,元素的值是value的移动。用于高效地添加临时对象或即将销毁的对象,避免不必要的拷贝。实现: 调用
emplace_back(std::move(value)),使用std::move()将value转换为右值引用,然后传递给emplace_back(),以触发移动构造。
emplace_back(params&&... p):模板参数:
params&&... p- 可变参数模板,接受任意数量、任意类型的参数,并完美转发 (perfect forwarding) 到元素类型的构造函数。功能: 在
vector的末尾就地构造一个新元素。与push_back的区别在于,emplace_back直接在vector的内存空间中构造元素,而不是先创建临时对象再拷贝或移动,通常更高效。扩容:
if (_size == _capacity)检查当前大小_size是否已达到容量_capacity。reserve(((_capacity + 1) * 3) >> 1);如果容量不足,则调用reserve()扩容。扩容策略是:新容量 =((_capacity + 1) * 3) / 2,即增加当前容量的 50% (再加上原来的容量)。>> 1是右移一位,等价于除以 2。(_capacity + 1)是为了防止当_capacity为 0 时,新容量仍然为 0。assert(_size < _capacity);扩容后,断言_size仍然小于新的_capacity。
就地构造:
new (std::addressof(_data[_size])) T(std::forward<params>(p)...);这是emplace_back的核心。std::addressof(_data[_size])获取vector内部数据数组_data的索引为_size的内存地址。注意,此时这块内存是未构造的。new (placement new)使用** placement new** 运算符。 placement new 的作用是在已分配但未构造的内存上就地构造对象。T(std::forward<params>(p)...)调用类型T的构造函数,并将可变参数p完美转发给T的构造函数。完美转发保留了参数的值类别 (左值或右值),确保使用最合适的构造函数版本 (例如,移动构造或拷贝构造)。
更新大小:
++_size;构造成功后,将vector的大小_size增加 1。返回值:
return _data[_size - 1];返回指向新构造元素的指针。_size已经增加,所以_size - 1是新添加元素的索引。
erase(u64 index):参数:
index(u64类型) - 要删除的元素的索引。功能: 删除
vector中指定索引位置的元素。删除后,后续元素会向前移动以填补空位,保持元素的连续存储。断言:
assert(_data && index < _size);确保_data指针有效且索引index有效。调用
erase(T* const item): 实际删除操作委托给重载版本的erase(T* const item)函数,并将指向要删除元素的指针std::addressof(_data[index])传递给它。
erase(T* const item):参数:
item(T* const类型) - 指向要删除元素的指针。功能: 删除
vector中指针item指向的元素。断言:
assert(_data && item >= std::addressof(_data[0]) && item < std::addressof(_data[_size]));确保_data指针有效,且item指针指向vector内部数据数组的有效元素范围内。元素析构 (条件编译):
if constexpr (destruct) item->~T();如果destruct模板参数为true,则显式调用要删除元素的析构函数item->~T();。减小大小:
--_size;vector的大小减 1。元素前移 (如果需要):
if (item < std::addressof(_data[_size])) { memcpy(item, item + 1, (std::addressof(_data[_size]) - item) * sizeof(T)); }if (item < std::addressof(_data[_size]))判断要删除的元素是否不是最后一个元素。如果是最后一个元素,则不需要移动元素。memcpy(item, item + 1, (std::addressof(_data[_size]) - item) * sizeof(T));如果不是最后一个元素,则使用memcpy()函数将要删除元素之后的所有元素向前移动一个位置。item- 目标地址,即要删除元素的位置。item + 1- 源地址,即要删除元素之后的位置。(std::addressof(_data[_size]) - item) * sizeof(T)- 要复制的字节数,即从要删除元素之后的位置到vector最后一个元素 (但不包括最后一个元素) 的所有元素的大小总和。
返回值:
return item;返回指向已删除元素的指针item。虽然元素已经被删除和移动,但返回指针本身可能在某些特殊情况下有用 (例如,用于迭代器失效处理)。
erase_unordered(u64 index):参数:
index(u64类型) - 要删除元素的索引。功能: 无序删除
vector中指定索引位置的元素。与erase()的区别在于,erase_unordered()为了追求更高的删除效率,不保证删除元素后元素的顺序。它通常将最后一个元素移动到要删除元素的位置,然后直接删除最后一个元素。断言:
assert(_data && index < _size);确保索引有效。调用
erase_unordered(T* const item): 实际操作委托给重载版本,传递要删除元素的指针。
erase_unordered(T* const item):参数:
item(T* const类型) - 指向要删除元素的指针。功能: 无序删除指针
item指向的元素。断言:
assert(_data && item >= std::addressof(_data[0]) && item < std::addressof(_data[_size]));确保指针有效。元素析构 (条件编译):
if constexpr (destruct) item->~T();条件析构。减小大小:
--_size;大小减 1。元素替换 (如果需要):
if (item < std::addressof(_data[_size])) { memcpy(item, std::addressof(_data[_size]), sizeof(T)); }if (item < std::addressof(_data[_size]))判断要删除的元素是否不是最后一个元素。memcpy(item, std::addressof(_data[_size]), sizeof(T));如果是,则使用memcpy()将最后一个元素 (位于_data[_size], 注意_size此时已经减 1 了,但这里使用的是减 1 之前的_size,即原vector的最后一个元素的地址) 拷贝到要删除元素的位置item。 这样就用最后一个元素覆盖了要删除的元素。
返回值:
return item;返回指向已删除元素位置的指针。
clear():功能: 移除
vector中的所有元素,使其变为空vector。但它不释放已分配的内存空间 (容量_capacity不变)。元素析构 (条件编译):
if constexpr (destruct) { destruct_range(0, _size); }如果destruct为true,则调用destruct_range(0, _size)析构所有元素。重置大小:
_size = 0;将_size设置为 0,表示vector中不再包含任何有效元素。
swap(vector& o):参数:
o(vector&类型) - 要交换的另一个vector对象的引用。功能: 交换当前
vector对象和另一个vector对象o的内容。交换的内容包括_capacity,_size, 和_data指针。自交换检查:
if (this != std::addressof(o))防止与自身交换,如果尝试vec.swap(vec);,则直接返回。交换实现:
auto temp(o);创建一个o的拷贝temp(注意这里是拷贝,不是移动,因为要保证交换的正确性)。o = *this;将当前vector对象的内容赋值给o(这里是赋值,会调用拷贝赋值或移动赋值,取决于*this是左值还是右值。在swap函数中,*this是左值,所以会调用拷贝赋值)。*this = temp;将临时拷贝temp的内容赋值给当前vector对象 (同样是拷贝赋值)。
注意: 这种
swap的实现方式实际上是通过拷贝和赋值来模拟交换,效率可能不如直接交换内部指针的方式高。更高效的swap实现通常会直接交换_capacity,_size, 和_data这三个成员变量。 (代码中的swap实现可能需要修正为更高效的版本,直接交换内部成员)
9. 总结
XEngine::utl::vector 类提供了一个基本的动态数组实现,包含:
多种构造方式: 默认构造,计数构造,带值计数构造,拷贝构造,移动构造,迭代器范围构造。
动态内存管理: 使用
realloc动态调整容量,reserve预留空间,destroy和clear清理资源。元素访问:
operator[],front,back,data,begin,end等方法。修改操作:
push_back,emplace_back,resize,erase,erase_unordered,clear,swap。可定制的析构行为: 通过模板参数
destruct控制是否在元素移除时调用析构函数。
与 std::vector 的对比:
相似之处: 功能上类似,都提供了动态数组的基本操作。
不同之处:
定制化:
XEngine::utl::vector提供了destruct模板参数,允许用户控制析构行为,这在某些特殊场景下可能有用。内存管理: 使用了
realloc和free进行内存管理,可能在某些方面与std::vector的内存分配策略有所不同。异常安全性: 代码中使用了
assert进行断言检查,主要用于开发阶段的错误检测。在生产环境中,std::vector通常提供更完善的异常安全性保证。效率:
std::vector经过高度优化,通常在各种场景下都具有良好的性能。自定义的vector可能在某些特定场景下为了追求极致性能而做出权衡。功能完整性:
std::vector是 C++ 标准库的一部分,功能更全面,提供了更多的成员函数和算法支持。