这份教程将深入探讨 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 是空的,capacitysize 均为 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):

    • 同样使用 explicitconstexpr

    • 参数:

      • 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 对象的常量引用。

    • 功能: 执行深拷贝。它会分配新的内存,并将源 vector o 中的所有元素拷贝到新的 vector 中。

    • 用法:

    C++

    XEngine::utl::vector<int> vec4 = vec2; // 使用拷贝构造函数,vec4 是 vec2 的一个副本
    
  • 移动构造函数 constexpr vector(const vector&& o):

    • 参数: o ( const vector&& 类型) - 要移动的 vector 对象的右值引用。

    • 功能: 执行浅拷贝(移动语义)。它会将源 vector o 的内部指针(_capacity, _size, _data移动到新的 vector,而不会复制元素。之后,源 vector o 会被置于有效但未指定的状态 (通过 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_tstd::_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):

    • 功能:

      1. 断言检查: assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }()); 这是一个复杂的断言,用于检查内部状态的一致性。它确保如果 _capacity 大于 0 (即分配了内存),则 _data 指针不能为 nullptr;反之,如果 _capacity 为 0,则 _data 必须为 nullptr。这有助于在开发阶段尽早发现内存管理错误。

      2. 清理元素: 调用 clear() 函数来析构 vector 中所有已构造的元素(如果 destruct 模板参数为 true)。

      3. 释放内存:

        • _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 模板参数 destructtrue 时,才会执行元素的析构。

    • 断言: assert(destruct); assert(first <= _size && last <= _size && first <= last); 确保 destructtrue,且范围索引有效。

    • 析构循环: 如果 _data 指针有效,则循环遍历索引从 firstlast 的元素,并对每个元素调用析构函数 _data[first].~T();

4. 赋值运算符

  • 拷贝赋值运算符 constexpr vector& operator=(const vector& o):

    • 参数: o ( const vector& 类型) - 要赋值的源 vector 对象的常量引用。

    • 功能: 实现深拷贝赋值。

      1. 自赋值检查: assert(this != std::addressof(o)); if (this != std::addressof(o)) 防止自赋值,如果尝试 vec = vec;,则直接返回 *this

      2. 清理现有资源: clear(); 首先清理当前 vector 对象已有的元素 (并根据 destruct 模板参数决定是否析构)。

      3. 预留空间: reserve(o._size); 为新的元素预留足够的容量,大小与源 vector o 相同,以减少后续 emplace_back 操作中的内存重新分配。

      4. 元素拷贝: for (auto& item : o) { emplace_back(item); } 遍历源 vector o 的每个元素,并使用 emplace_back 将其拷贝到当前 vector 中。emplace_back(item) 会调用类型 T 的拷贝构造函数。

      5. 大小断言: assert(_size == o._size); 赋值完成后,断言当前 vector 的大小与源 vector 的大小相同,确保拷贝正确完成。

    • 返回值: return *this; 返回当前 vector 对象的引用,支持链式赋值操作 (例如 vec1 = vec2 = vec3;)。

  • 移动赋值运算符 constexpr vector& operator=(vector&& o):

    • 参数: o ( vector&& 类型) - 要赋值的源 vector 对象的右值引用。

    • 功能: 实现移动赋值。

      1. 自赋值检查: assert(this != std::addressof(o)); if (this != std::addressof(o)) 防止自赋值。

      2. 销毁现有资源: destroy(); 销毁当前 vector 对象已有的资源 (包括已分配的内存和元素)。

      3. 资源移动: move(o); 调用私有成员函数 move(o) 将源 vector o 的内部指针 (_capacity, _size, _data) 移动到当前 vector 对象。

    • 返回值: return *this; 返回当前 vector 对象的引用,支持链式赋值。

5. 容量操作

  • reserve(u64 new_capacity):

    • 参数: new_capacity ( u64 类型) - 新的容量大小。

    • 功能: 预留至少能容纳 new_capacity 个元素的内存空间。

      1. 容量检查: if (new_capacity > _capacity) 只有当 new_capacity 大于当前容量 _capacity 时,才需要重新分配内存。

      2. 内存重分配: void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) }; 使用 realloc() 函数尝试重新分配内存。realloc() 的优点是,如果可能,它会在原有内存块的基础上扩展内存,如果无法扩展,则会分配新的内存块,并将原有数据拷贝到新的内存块中。

      3. 断言检查: assert(new_buffer); 断言内存分配成功。如果 realloc() 失败返回 nullptr,则程序会终止。

      4. 更新内部状态:

        • 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) (非 constconst 版本):

    • 参数: 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() (非 constconst 版本):

    • 功能: 访问 vector第一个元素

    • 断言: assert(_data && _size); 断言 _data 指针有效且 vector 不为空 ( _size > 0)。

    • 返回值:

      • const 版本: T& - 返回第一个元素的引用 (左值引用)。

      • const 版本: const T& - 返回第一个元素的常量引用 (常量左值引用)。

    • 用法:

    C++

    int first_element = vec.front(); // 获取第一个元素
    
  • back() (非 constconst 版本):

    • 功能: 访问 vector最后一个元素

    • 断言: assert(_data && _size); 断言 _data 指针有效且 vector 不为空。

    • 返回值:

      • const 版本: T& - 返回最后一个元素的引用 (左值引用)。

      • const 版本: const T& - 返回最后一个元素的常量引用 (常量左值引用)。

    • 用法:

    C++

    int last_element = vec.back(); // 获取最后一个元素
    
  • data() (非 constconst 版本):

    • 功能: 获取指向 vector 内部原始数据数组的指针。

    • 返回值:

      • const 版本: T* - 返回指向第一个元素的const 指针,允许通过指针修改元素。

      • const 版本: T *const - 返回指向第一个元素的 const 指针,只允许通过指针读取元素,不能修改。

    • 用法:

    C++

    int* data_ptr = vec.data(); // 获取指向 vector 数据的指针
    

7. 迭代器

  • begin() (非 constconst 版本):

    • 功能: 返回指向 vector 起始位置的迭代器。

    • 断言: assert(_data); 断言 _data 指针有效。

    • 返回值:

      • const 版本: T* - 返回指向第一个元素的指针,作为非 const 迭代器。

      • const 版本: const T* - 返回指向第一个元素的 const 指针,作为 const 迭代器。

  • end() (非 constconst 版本):

    • 功能: 返回指向 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); } 如果 destructtrue,则调用 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);,则直接返回。

    • 交换实现:

      1. auto temp(o); 创建一个 o拷贝 temp (注意这里是拷贝,不是移动,因为要保证交换的正确性)。

      2. o = *this; 将当前 vector 对象的内容赋值o (这里是赋值,会调用拷贝赋值或移动赋值,取决于 *this 是左值还是右值。在 swap 函数中,*this 是左值,所以会调用拷贝赋值)。

      3. *this = temp; 将临时拷贝 temp 的内容赋值给当前 vector 对象 (同样是拷贝赋值)。

      • 注意: 这种 swap 的实现方式实际上是通过拷贝和赋值来模拟交换,效率可能不如直接交换内部指针的方式高。更高效的 swap 实现通常会直接交换 _capacity, _size, 和 _data 这三个成员变量。 (代码中的 swap 实现可能需要修正为更高效的版本,直接交换内部成员)

9. 总结

XEngine::utl::vector 类提供了一个基本的动态数组实现,包含:

  • 多种构造方式: 默认构造,计数构造,带值计数构造,拷贝构造,移动构造,迭代器范围构造。

  • 动态内存管理: 使用 realloc 动态调整容量,reserve 预留空间,destroyclear 清理资源。

  • 元素访问: operator[], front, back, data, begin, end 等方法。

  • 修改操作: push_back, emplace_back, resize, erase, erase_unordered, clear, swap

  • 可定制的析构行为: 通过模板参数 destruct 控制是否在元素移除时调用析构函数。

std::vector 的对比:

  • 相似之处: 功能上类似,都提供了动态数组的基本操作。

  • 不同之处:

    • 定制化: XEngine::utl::vector 提供了 destruct 模板参数,允许用户控制析构行为,这在某些特殊场景下可能有用。

    • 内存管理: 使用了 reallocfree 进行内存管理,可能在某些方面与 std::vector 的内存分配策略有所不同。

    • 异常安全性: 代码中使用了 assert 进行断言检查,主要用于开发阶段的错误检测。在生产环境中,std::vector 通常提供更完善的异常安全性保证。

    • 效率: std::vector 经过高度优化,通常在各种场景下都具有良好的性能。自定义的 vector 可能在某些特定场景下为了追求极致性能而做出权衡。

    • 功能完整性: std::vector 是 C++ 标准库的一部分,功能更全面,提供了更多的成员函数和算法支持。