EASTL源码分析
开局
EASTL 主要由容器、算法和迭代器组成。
容器的一个例子是链表,而算法的一个例子是排序函数;迭代器则是用于遍历容器和算法的实体。
EASTL包含相当多的容器和算法,每个都是一个非常干净、高效且经过单元测试的实现。
我们可以自信地说,你不太可能找到更好的实现(无论是商业还是其他),因为这些实现是多年智慧和勤奋工作的结果。
EAST 包含并扩展了标准 C++ STL 的功能,同时以各种对游戏开发有用的方式对其进行改进。
EASTL 的大部分设计与标准 STL 相同,因为 STL 的大部分设计都经过精心设计,可用于多种用途。
EASTL 与标准 STL 实现不同的主要方面基本上如下:
● 具有简化且更灵活的自定义分配方案。
● 代码明显更易于阅读。
● 具有扩展容器和算法。
● 具有专为游戏开发而设计的优化。
唯一与STL不兼容的差异是内存分配的情况。
为EASTL定义自定义分配器的方法与标准STL略有不同,尽管它们有90%的相似度。
然而,那10%的不同之处却使得EASTL在大多数情况下比标准STL更易于使用且功能更强大。
没有自定义分配器的容器在EASTL和标准STL之间的行为是相同的。
● 某些 STL 实现(尤其是 Microsoft STL)的性能特征较差,因此不适合游戏开发。EASTL 比所有现有的 STL 实现都快。
● STL 有时很难调试,因为大多数 STL 实现都使用晦涩难懂的变量名称和不寻常的数据结构。
● STL 分配器有时使用起来很痛苦,因为它们有很多要求,并且一旦绑定到容器就无法修改。
● STL 包含过多的功能,这些功能可能会导致代码大于预期。告诉程序员他们不应该使用该功能并不容易。
● STL 是通过非常深入的函数调用实现的。这会导致在未优化构建中不可接受的性能,有时在优化构建中也是如此。
● STL 不支持包含对象的对齐。
● STL 容器不允许在未提供要从中复制的条目的情况下将条目插入容器。这可能效率低下。
● 在现有 STL 实现(如 STLPort)中找到的有用的 STL 扩展(例如 slist、hash_map shared_ptr)是不可移植的,因为它们在其他版本的 STL 中不存在,或者在 STL 版本之间不一致。
● STL 缺乏游戏程序员认为有用的有用扩展(例如 intrusive_list),但在便携式 STL 环境中可以最好地优化这些扩展。
● STL 的规范限制了我们有效使用它的能力。例如,STL 向量不能保证使用连续内存,因此不能安全地用作数组。
● STL 强调正确性而不是性能,而有时您可以通过降低学术纯粹性来获得显着的性能提升。
● STL 容器具有私有实现,不允许您以可移植的方式处理其数据,但有时这是一件重要的事情(例如节点池)。
● 所有现有版本的 STL 都至少在其某些容器的空版本中分配内存。这并不理想,并且会阻止优化,例如容器内存重置,在某些情况下可以大大提高性能。
● STL 编译速度很慢,因为大多数现代 STL 实现都非常大。
● 存在一些法律问题,使我们很难自由地使用可移植的 STL 实现,例如 STLPort。
● 我们在 STL 的设计和实施中没有发言权,因此无法改变它以满足我们的需求。
可读性是 EASTL 比许多其他模板化库(尤其是 Microsoft STL 和 STLPort)更好的实现。
我们尽一切努力使 EASTL 代码清晰明了。有时我们需要提供优化(特别是与 type_traits 和 iterator 类型相关)会导致代码不那么简单,但效率恰好是我们的首要任务,因此它凌驾于所有其他考虑因素之上。
容器
● 原型容器:定义了所有非适配器容器都必须实现的基本功能。
● 适配器容器:如栈、队列等,这些容器通常基于其他基础容器构建,并且其具体实现可能会有所不同。
● 一致性:通过设定统一的标准,EASTL确保了不同容器之间的兼容性和易用性,减少了学习和使用成本。
template <class T, class Allocator = EASTLAllocator>
class container
{
public:
typedef container<T, Allocator> this_type;
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef ptrdiff_t difference_type;
typedef impl_defined size_type;
typedef impl-defined iterator;
typedef impl-defined const_iterator;
typedef reverse_iterator<iterator> reverse_iterator;
typedef reverse_iterator<const_iterator> reverse_const_iterator;
typedef Allocator allocator_type;
public:
container(const allocator_type& allocator = allocator_type());
container(const this_type& x);
this_type& operator=(this_type& x);
void swap(this_type& x);
void reset();
allocator_type& get_allocator();
void set_allocator(allocator_type& allocator);
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
bool validate() const;
int validate_iterator(const_iterator i) const;
protected:
allocator_type mAllocator;
};
template <class T, class Allocator>
bool operator==(const container<T, Allocator>& a,
const container<T, Allocator>& b);
template <class T, class Allocator>
bool operator!=(const container<T, Allocator>& a,
const container<T, Allocator>& b);
● 分配器不交换:当两个容器通过交换操作互换内容时,它们各自的分配器不会被交换,这意味着每个容器将继续使用其原来的分配策略。
● 避免不必要的内存分配:EASTL的设计原则之一是尽量减少不必要的内存分配。新构造的空容器不会预先分配内存,这与某些其他容器库不同,后者可能会预先分配一个初始节点。
● 空容器的状态:无论是新创建的还是已经存在的空容器,都不会包含任何已构造的对象,包括所谓的“结束”节点。只有在设计确实需要并且有文档说明的情况下,才会构造用户对象。
● reset函数的作用:reset函数用于快速重置容器到空状态,而无需释放容器内对象占用的内存。这对于临时使用的容器特别有用,因为它可以快速清理容器而不涉及复杂的内存管理。
● 显式验证机制:为了平衡性能和安全性,EASTL提供了显式的验证功能(如validate和validate_iterator),允许用户在需要时手动检查容器和迭代器的有效性,而不是每次都自动进行这种可能耗费大量资源的检查。这种方法既保证了灵活性,也确保了性能。
通过这些设计,EASTL旨在提高性能和易用性,同时为开发者提供更多的控制权和灵活性。
分配器
STL分配器的问题:
● 类级别定义:STL容器的分配器是在类级别定义的,而不是在实例级别,这使得为每个容器实例定义不同的分配器变得复杂。
● 代码膨胀:由于分配器模板需要针对每种类型重新绑定 allocator<T>::rebind<U>::other 这导致了代码膨胀。
● 不可访问性:容器构造后,你无法再访问其分配器,这限制了对分配器的操作和管理。
EASTL的改进:
● 单一分配器接口:EASTL采用了一个统一的分配器类接口,所有容器都使用这个接口,简化了内存分配的管理和使用。
● 灵活的分配器操作:EASTL容器允许用户访问、查询、命名和更改分配器,提供了更大的灵活性和控制权。
分配器在容器操作中的行为:
● 交换操作:当容器A与容器B交换内容时,两个容器都会保留其原始分配器,而不是交换分配器。
● 赋值操作:将容器A赋值给容器B时,容器B会保留其原始分配器,而不是继承容器A的分配器。
● 智能交换:如果两个容器的分配器相同,EASTL会执行智能交换(更高效的方式)。如果分配器不同,则执行暴力交换(逐元素复制)。
通过这些改进,EASTL不仅解决了STL中分配器相关的一些痛点,还提高了内存管理的灵活性和效率,使开发者能够更好地控制容器的行为和性能。
// EASTL allocator
class allocator
{
public:
allocator(const char* pName = NULL);
void* allocate(size_t n, int flags = 0);
void* allocate(size_t n, size_t alignment, size_t offset, int flags = 0);
void deallocate(void* p, size_t n);
const char* get_name() const;
void set_name(const char* pName);
};
allocator* GetDefaultAllocator();
固定大小的容器
EASTL提供了诸如fixed_list这样的固定大小容器,它们通过固定大小的连续内存池来实现。
fixed_list具有以下特点:
● 没有额外的空间开销。
● 不会导致内存碎片化。
● 分配速度非常快。
实现方式:
● EASTL通过继承自常规容器的子类来实现固定容器,这些子类将其常规容器的分配器设置为指向自身。
● 这种设计使得fixed_list的实现非常简洁,主要包括构造函数和分配器函数。
设计的优点:
● 减少代码膨胀:由于实现简单,避免了大量的重复代码。
● 易于扩展:用户可以轻松地基于现有的实现进行扩展和修改。
● 简化实现:保持了实现的简单性和清晰度。
设计的小缺点:
● 父类list中有一个指向自身的指针,占用了4个字节的空间。
● 如果采用不同的设计(例如策略模板参数),可以节省这4个字节,但会带来其他问题,如实现复杂化、用户扩展难度增加以及可能的代码膨胀。
为什么不采用策略设计:
● 实现复杂化:策略设计会使容器的实现更加复杂。
● 用户扩展难度增加:复杂的实现会使用户难以扩展和修改容器。
● 潜在的代码膨胀:虽然可以节省每个容器实例中的4字节空间,但由于策略设计可能导致更多的代码膨胀,反而浪费了更多的内存。
通过这种方式,EASTL在简化实现和提高灵活性的同时,权衡了一些小的性能损失,确保了整体的设计简洁性和易用性。
算法
算法设计哲学:
● EASTL算法遵循标准C++算法的设计原则,强调使用迭代器而非容器。
● 通过使用迭代器,算法可以处理容器的任意子范围,并且可以应用于非容器的数据结构(如C数组)。
灵活性和通用性:
● 这种设计使得算法更加灵活和通用,用户可以轻松指定要操作的数据范围,而不需要局限于特定类型的容器。
● 例如,find算法接受两个迭代器参数,允许在任意范围内查找元素。
性能优化:
● EASTL算法不仅与商业库中的最佳STL算法一样优化,而且在很多情况下表现更好。
● EASTL算法利用类型特征和迭代器类型进行优化,以生成更高效的代码。例如,对于整数数组或POD类型的数据,EASTL会在适当的情况下使用memcpy来代替逐对象复制,从而提高性能。
复杂性和实现难度:
● EASTL算法和相关支持代码使用了一些高级的C++技巧,尽管代码本身易于阅读,但实现和维护这些优化需要深厚的C++知识和经验。
● 开发和维护EASTL相比简单的库需要更多的努力和专业知识,但由于其带来的性能提升,这种权衡被认为是值得的。
总结来说,EASTL通过精心设计的算法和高级优化技术,在保持灵活性和通用性的同时,显著提升了性能。尽管这增加了开发和维护的复杂性,但最终的性能优势使其成为高性能应用的理想选择。
配置
使用EASTL要求覆盖全局new
void* __cdecl operator new[](size_t size, const char* name, int flags, unsigned debugFlags, const char* file, int line)
{
return new uint8_t[size];
}
void* __cdecl operator new[](unsigned __int64 size, unsigned __int64 alignment, unsigned __int64 offset, char const* pName, int flags, unsigned int debugFlags, char const* file, int line)
{
return new uint8_t[size];
}
容器
Array
Array
● 实现了一个符合C++标准TR1的模板数组类。
● 这个类允许你像使用STL vector一样使用内置的C风格数组。
● 它不允许你改变其大小,因为它就像一个C内置数组一样。
● 我们的实现努力去除函数调用嵌套,因为这会使我们在调试构建中由于函数调用开销而难以进行性能分析。
● 注意,根据C++标准更新提案的要求,这里故意将其定义为一个具有公共数据的结构体(struct)。
使用示例:
eastl::array<int, 5> arr{1,2,3,4,5};
for (auto value : arr)
{
cout << value << '\n';
}
类型别名:
template <typename T, size_t N = 1>
struct array
{
public:
typedef array<T, N> this_type;
typedef T value_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef eastl::reverse_iterator<iterator> reverse_iterator;
typedef eastl::reverse_iterator<const_iterator> const_reverse_iterator;
typedef eastl_size_t size_type; // See config.h for the definition of eastl_size_t, which defaults to size_t.
typedef ptrdiff_t difference_type;
}
using ArrayTest = eastl::array<int,5>;
// eastl::array<int, 5>
using Array_this_type = ArrayTest::this_type;
//BEGIN—— 类型别名 都来自于T ——BEGIN
// int
using Array_value_type = ArrayTest::value_type;
// int&
using Array_reference = ArrayTest::reference;
//const int&
using Array_const_reference = ArrayTest::const_reference;
// int*
using Array_iterator = ArrayTest::iterator;
// const int*
using Array_const_iterator = ArrayTest::const_iterator;
// eastl::reverse_iterator<int*>
using Array_reverse_iterator = ArrayTest::reverse_iterator;
// eastl::reverse_iterator<const int*>
using Array_const_reverse_iterator = ArrayTest::const_reverse_iterator;
//END—— 类型别名 都来自于T ——END
// unsigned long long
using Array_size_type = ArrayTest::size_type;
// long long
using Array_difference_type = ArrayTest::difference_type;
聚合初始化:
// Note that the member data is intentionally public.
// This allows for aggregate initialization of the
// object (e.g. array<int, 5> a = { 0, 3, 2, 4 }; )
// do not use this member directly (use data() instead).
value_type mValue[N];
● 注意,成员数据有意是公开的。
● 这允许对对象进行聚合初始化(例如,array<int, 5> a = {0,3,2,4};)
● 不要直接使用这个成员(使用data()代替)。
eastl::array 维护了一个C风格数组.
这个eastl::array类没有显式定义构造函数,是为了保持其作为聚合类型的特性,从而允许通过聚合初始化直接初始化其公有成员数组mValue。
源码注释里: 故意不提供构造函数、析构函数或赋值操作符。
聚合类型的定义:C++规定,满足以下条件的类或结构体是聚合类型:
a. 没有用户提供的构造函数(包括默认构造函数);
b. 所有非静态成员都是公有的;
c. 没有基类和虚函数。
eastl::array符合这些条件,因此它是一个聚合类型。
支持聚合初始化:聚合类型允许通过花括号列表直接初始化成员,无需定义构造函数。
struct S { int a; int b; };
S s = {1, 2}; // 直接初始化公有成员a和b
//-----------------//
struct my_struct
{
int Valueint = 1;
int mValue[2];
float Valuefloat = 2 ;
};
my_struct s = {.mValue= {2, 3}, .Value= 2.5};
my_struct s2 = {2,{4,5},7.2};
eastl::array<int, 5> arr {1, 2, 3, 4, 5};
● 聚合初始化的作用:初始化列表{1, 2, 3, 4, 5}会直接赋值给mValue数组的对应元素。
● 底层实现:编译器将初始化列表中的值按顺序填充到mValue的每个位置,等同于:
● value_type mValue[5] = {1, 2, 3, 4, 5};
如果初始化列表元素少于N,剩余元素会默认初始化(如int类型初始化为0);
若多于N,编译器会报错。
● std::array的设计一致性:C++标准库的std::array同样没有显式构造函数,依赖聚合初始化。eastl::array遵循这一设计,确保接口兼容性和性能优化。
● 性能优势:避免构造函数的调用开销,编译器可直接生成高效的初始化代码。
总结:
eastl::array不定义构造函数,是为了保持聚合类型的特性,允许用户通过花括号初始化列表直接填充公有成员数组mValue。
这种设计简化了实现,提高了效率,并与标准库的std::array行为一致。
初始化时,编译器直接将列表值按顺序赋给mValue,无需构造函数介入。
Vector
使用示例:
int main()
{
eastl::vector<int> vec;
vec.push_back(127);
vec.push_back(2);
vec.push_back(3);
vec.push_back(355);
for (auto& i : vec)
{
// 输出 127 2 3 355
cout << i << " ";
}
cout << '\n';
auto begin = vec.begin();
auto end = vec.end()-1;
// begin:127 end:355
cout << "begin:" << *begin <<" end:"<<*end << '\n';
//size:4
cout<< format("size:{} \n",vec.size());
//capacity:4 rbegin:355 rend:127
cout<< format("capacity:{} rbegin:{} rend:{} \n",vec.capacity(),*vec.rbegin(),*(vec.rend()-1));
vec.reserve(10);
//size:4 capacity:10
cout << format("size:{} capacity:{}\n",vec.size(),vec.capacity());
//begin:127 end:355
cout << format("begin:{} end:{} \n",*vec.begin(),*(vec.end()-1));
}
// 输出:
// 127 2 3 355
// begin:127 end:355
// size:4
// capacity:4 rbegin:355 rend:127
// size:4 capacity:10
// begin:127 end:355
● size 当前元素个数
● capacity 当前容量
● vec容量为10,但只有3个元素,那么size = 3,capacity = 10
vector<T, Allocator>::size() const EA_NOEXCEPT
{
return (size_type)(mpEnd - mpBegin);
}
vector<T, Allocator>::capacity() const EA_NOEXCEPT
{
return (size_type)(internalCapacityPtr() - mpBegin);
}
//---------------------------//
eastl::compressed_pair<T*, allocator_type> mCapacityAllocator;
T*& internalCapacityPtr() EA_NOEXCEPT { return mCapacityAllocator.first(); }
T* const& internalCapacityPtr() const EA_NOEXCEPT { return mCapacityAllocator.first(); }
size = end - begin.
例如:vector里面有5个元素: 1,2,3,4,5 . begin 指向1 , end指向5的下一个位置.
Capacity = 整个空间 - begin. 即为vector的整个空间容量.
internalCapacityPtr():返回指向当前分配的内存块的容量结束位置的指针。
vector 创建时size=0,capacity=0
vector 有一个元素,触发扩容,size=1,capacity=1
vector 当后续再插入元素 空间不够时,整个空间扩容2倍.
插入与扩容
inline void vector<T, Allocator>::push_back(const value_type& value)
{
if(mpEnd < internalCapacityPtr())
::new((void*)mpEnd++) value_type(value);
else
DoInsertValueEnd(value);
}
● mpEnd:指向当前 vector 中最后一个已使用的元素之后的位置。
● internalCapacityPtr():返回指向当前分配的内存块的容量结束位置的指针。
该条件检查当前 vector 是否有足够的空间来容纳新元素。
如果 mpEnd 小于 internalCapacityPtr(),则表示当前容量足够,可以直接插入新元素。
::new((void*)mpEnd++) value_type(value); 放置式 new 操作符,用于在指定地址上构造对象。这里使用的是 mpEnd 所指向的地址,并且在构造后将 mpEnd 向前移动一位。
DoInsertValueEnd(value); 当前容量不足时,调用此函数处理插入操作。
template <typename T, typename Allocator>
template<typename... Args>
void vector<T, Allocator>::DoInsertValueEnd(Args&&... args)
{
const size_type nPrevSize = size_type(mpEnd - mpBegin);
const size_type nNewSize = GetNewCapacity(nPrevSize);
pointer const pNewData = DoAllocate(nNewSize);
pointer pNewEnd = eastl::uninitialized_move(mpBegin, mpEnd, pNewData);
::new((void*)pNewEnd) value_type(eastl::forward<Args>(args)...);
pNewEnd++;
eastl::destruct(mpBegin, mpEnd);
DoFree(mpBegin, (size_type)(internalCapacityPtr() - mpBegin));
mpBegin = pNewData;
mpEnd = pNewEnd;
internalCapacityPtr() = pNewData + nNewSize;
}
template <typename T, typename Allocator>
inline typename VectorBase<T, Allocator>::size_type
VectorBase<T, Allocator>::GetNewCapacity(size_type currentCapacity)
{
// This needs to return a value of at least currentCapacity and at least 1.
return (currentCapacity > 0) ? (2 * currentCapacity) : 1;
}
GetNewCapacity 如果当前容量为0,则扩容为1,其它情况下都扩容2倍.
- 计算当前大小与新容量
● 当前大小:通过 mpEnd - mpBegin 获取当前元素数量。
● 新容量:调用 GetNewCapacity(nPrevSize) 确定扩容后的新容量(通常按固定策略,如翻倍)。 - 分配新内存
● 使用 DoAllocate(nNewSize) 分配大小为 nNewSize 的新内存块 pNewData。 - 迁移旧数据
● 移动构造:通过 eastl::uninitialized_move 将旧数据移动到新内存。这一步调用元素的移动构造函数,转移资源所有权。
● 构造新元素:在新内存末尾(pNewEnd)使用 placement new 和完美转发 Args&&... 直接构造新元素,避免额外拷贝。 - 清理旧内存
● 析构旧元素:调用 eastl::destruct 析构旧内存中的元素(移动后的源对象仍需析构,但资源已转移)。
● 释放旧内存:通过 DoFree 释放原内存块,需传入原指针和容量(确保内存正确释放)。 - 更新内部指针
● mpBegin:指向新内存起始位置。
● mpEnd:指向新内存的末尾(即最后一个元素的下一个位置)。
● internalCapacityPtr():更新为新内存的容量终点(pNewData + nNewSize)。
右值版本 调用移动构造:
template <typename T, typename Allocator>
inline void vector<T, Allocator>::push_back(value_type&& value)
{
if (mpEnd < internalCapacityPtr())
::new((void*)mpEnd++) value_type(eastl::move(value));
else
DoInsertValueEnd(eastl::move(value));
}
- 右值引用:如果传递的是右值引用(rvalue reference),则会调用移动构造函数。
- 左值引用:如果传递的是左值引用(lvalue reference),则会调用拷贝构造函数。
List
string
hash_set
智能指针
智能指针(Smart Pointer)是 C++ 中用于管理动态分配对象生命周期的一种工具。
通过封装原始指针,并利用 RAII 机制自动管理内存的分配和释放,从而避免常见的内存泄漏问题。
- RAII 机制
○ 资源获取即初始化:在对象创建时获取资源,在对象销毁时释放资源。
○ 作用域控制:智能指针在其作用域结束时自动释放所管理的对象,确保资源不会被遗忘或泄露。 - 自动内存管理
○ 自动释放:智能指针会在其生命周期结束时自动释放所管理的对象,减少手动管理内存的复杂性和出错的可能性。
○ 防止悬挂指针:智能指针能够检测到对象是否仍然被引用,从而防止悬挂指针的问题。 - 引用计数
○ 共享所有权:多个智能指针可以共享同一个对象的所有权,只有当最后一个指向该对象的智能指针被销毁时,才会释放该对象。
○ 线程安全:某些智能指针(如 std::shared_ptr)提供了线程安全的引用计数机制。 - 独占所有权
○ 单一所有权:某些智能指针(如 std::unique_ptr)确保只有一个指针拥有某个对象的所有权,防止多个指针同时管理同一对象。
unique_ptr
● 独占所有权:一个 std::unique_ptr 对象只能指向一个对象,不能进行拷贝操作,但可以通过移动语义转移所有权。
● 轻量级:相比于 std::shared_ptr,std::unique_ptr 更加轻量,因为它不涉及引用计数。
更具体地说,unique_ptr 模板类存储一个指向动态分配对象的指针。该对象在 unique_ptr 的析构函数中自动删除,或者可以通过 unique_ptr::reset 函数手动删除。
class Object
{
public:
int A = 0;
Object(int v) : A(v)
{
cout<<"CreateObj " << A <<"\n";
}
~Object()
{
cout<<"DestroyObj " << A <<"\n";
}
};
int main()
{
{
using ObjectPtr = eastl::unique_ptr<Object>;
Object* obj1 = new Object(100);
ObjectPtr objpt1(obj1);
ObjectPtr objpt2 = eastl::make_unique<Object>(200);
cout << format("objpt1: {} objpt2:{} \n", objpt1->A, objpt2->A);
// 交换 objpt1 和 objpt2 保存的指针
objpt1.swap(objpt2);
cout <<"----------objpt2.reset()--------------\n";
//reset - delete保存的指针.
//调用objpt2保存的Object对象的析构函数.
//然后,objpt2保存的指针是空指针.
objpt2.reset();
cout <<"----------Move Test--------------\n";
// 把ptr1的指针交给ptr2,
// ptr1指针为空,ptr2拥有原先ptr1的指针.
// 也可以说 ptr2接管了ptr1的指针.
ObjectPtr ptr1(new Object(400));
cout << format("ptr1: {}\n", ptr1->A);
ObjectPtr ptr2 = eastl::move(ptr1);
cout << "move ptr1 ---> ptr2 \n";
cout << format("ptr2: {}\n", ptr2->A);
if (auto ptr1object = ptr1.get())
{
cout << ptr1object->A <<'\n';
}
else
{
cout << "ptr1 is null\n";
}
cout <<"----------ptr2.reset(new Object(5000))--------------\n";
ptr2.reset(new Object(5000));
cout <<"----------Main End--------------\n";
}
while (true)
{
}
}
输出:
CreateObj 100
CreateObj 200
objpt1: 100 objpt2:200
----------objpt2.reset()--------------
DestroyObj 100
----------Move Test--------------
CreateObj 400
ptr1: 400
move ptr1 ---> ptr2
ptr2: 400
ptr1 is null
----------ptr2.reset(new Object(5000))--------------
CreateObj 5000
DestroyObj 400
----------Main End--------------
DestroyObj 5000
DestroyObj 200
release() 取出指针
unique_ptr返回指针,并且把自己保存的设为空.
using ObjectPtr = eastl::unique_ptr<Object>;
Object* Obj1 = new Object(100);
ObjectPtr ObjPtr(Obj1);
cout << format("Smart ObjPtr.A = {}\n", ObjPtr->A);
Object* ptr = ObjPtr.release();
cout << format("ptr.A = {}\n", ptr->A);
if (ObjPtr.get())
{
cout << format("objPtr.A = {}\n", ObjPtr->A);
}
else
{
cout << "ObjPtr is nullptr\n";
}
输出:
CreateObj 100
Smart ObjPtr.A = 100
ptr.A = 100
ObjPtr is nullptr
析构过程
//销毁已拥有的指针。
//被拥有的指针引用的对象的析构函数将被调用。
~unique_ptr() EA_NOEXCEPT
{
reset();
}
//删除已拥有的指针,并获取传入指针的所有权。
//如果传入的指针与拥有的指针相同,则什么也不做。
// 例如:
// unique_ptr<int> ptr(new int(3));
// ptr.reset(new int(4)); // deletes int(3)
// ptr.reset(NULL); // deletes int(4)
void reset(pointer pValue = pointer()) EA_NOEXCEPT
{
if (pValue != mPair.first())
{
if (auto first = eastl::exchange(mPair.first(), pValue))
get_deleter()(first);
}
}
deleter_type& get_deleter() EA_NOEXCEPT
{
return mPair.second();
}
void operator()(T* p) const EA_NOEXCEPT
{
static_assert(eastl::internal::is_complete_type_v<T>, "Attempting to call the destructor of an incomplete type");
delete p;
}
析构时调用reset函数,并且传入空指针.
eastl::exchange(mPair.first(), pValue) 交换两个指针,
假设 unique_ptr 保存的有效指针 unique_ptr -> ptr 为 指针A.
1.把 unique_ptr 里面保存的 指针A 与 nullptr 交换,此时 unique_ptr保存的是 nullptr.
unique_ptr -> ptr = nullptr
first= 指针A ,
2.get_deleter() 取出删除器.
删除器有两种,default_delete
3.调用 删除器 的 ()运算符重载,把 first 作为参数传递进去. 这个重载运算符将调用delete 删除 first.
template <typename T>
struct default_delete
{
EA_CONSTEXPR default_delete() EA_NOEXCEPT = default;
template <typename U> // Enable if T* can be constructed with U* (i.e. U* is convertible to T*).
default_delete(const default_delete<U>&, typename eastl::enable_if<is_convertible<U*, T*>::value>::type* = 0) EA_NOEXCEPT {}
void operator()(T* p) const EA_NOEXCEPT
{
static_assert(eastl::internal::is_complete_type_v<T>, "Attempting to call the destructor of an incomplete type");
delete p;
}
};
//-------------------//
template <typename T>
struct default_delete<T[]>
{
void operator()(T* p) const EA_NOEXCEPT
{ delete[] p; }
}
shared_ptr
共享所有权:多个 std::shared_ptr 对象可以共享同一个对象的所有权,每个 std::shared_ptr 都持有对对象的引用计数。
这个类实现了 C++11 标准库中的 shared_ptr 模板。
shared_ptr 类似于 C++ 标准库中的 unique_ptr,但不同之处在于它允许通过引用计数在多个实例之间共享指针。
shared_ptr 对象可以安全地进行拷贝,并且可以安全地用于 C++ 标准库容器中,如 std::vector 或 std::list。
这个类不是线程安全的,因为你不能同时从两个线程使用它的同一个实例,也不能同时使用拥有相同指针的两个独立实例。
使用标准的多线程互斥技术来解决前者的问题,并使用 shared_ptr_mt 来解决后者的问题。
请注意,这与 C++11 标准不一致。
● 如果你使用原始指针构造一个 shared_ptr,你不能仅用那个原始指针再构造另一个 shared_ptr。相反,你需要用最初创建的 shared_ptr 来构造额外的 shared_ptr。否则会导致崩溃。
● 使用 shared_ptr 是线程安全的,但它指向的对象并不自动是线程安全的。多个引用同一个对象的 shared_ptr 可以被多个线程任意使用。
● 你可以将单个 shared_ptr 在多个线程之间以所有方式共享,除了赋值操作。以下操作不是线程安全的,需要通过互斥锁或 shared_ptr 的原子函数来保护:
shared_ptr<Foo> pFoo;
// 线程 1:
shared_ptr<Foo> pFoo2 = pFoo;
// 线程 2:
pFoo = make_shared<Foo>();
shared_ptr:
template <typename T>
class shared_ptr
{
//...........
protected:
element_type* mpValue;
ref_count_sp* mpRefCount;
//.........
}
两个成员变量分别是 指针 和 引用计数块.
引用计数块以指针形式存储,原因就是它是 new创建的,本体在内存条里,可以用指针指向它.
这样就实现了 多个 shared_ptr 类拥有同一个引用计数块. 因为它们都可以获得 引用计数块的地址,并且用指针指向它.
引用计数
ref_count_sp
struct ref_count_sp {
atomic<int32_t> mRefCount; /// Reference count on the contained pointer. Starts as 1 by default.
atomic<int32_t> mWeakRefCount; /// Reference count on contained pointer plus this ref_count_sp object itself. Starts as 1 by default.
public:
ref_count_sp(int32_t refCount = 1, int32_t weakRefCount = 1) EA_NOEXCEPT;
virtual ~ref_count_sp() EA_NOEXCEPT {}
int32_t use_count() const EA_NOEXCEPT;
void addref() EA_NOEXCEPT;
void release();
void weak_addref() EA_NOEXCEPT;
void weak_release();
ref_count_sp* lock() EA_NOEXCEPT;
virtual void free_value() EA_NOEXCEPT = 0; // Release the contained object.
virtual void free_ref_count_sp() EA_NOEXCEPT = 0; // Release this instance.
#if EASTL_RTTI_ENABLED
virtual void* get_deleter(const std::type_info& type) const EA_NOEXCEPT = 0;
#else
virtual void* get_deleter() const EA_NOEXCEPT = 0;
#endif
};
ref_count_sp 拥有两个原子变量
● 强引用计数 (mRefCount):跟踪有多少个 shared_ptr 实例指向同一个对象。
● 弱引用计数 (mWeakRefCount):跟踪有多少个 weak_ptr 实例指向同一个对象。
release() 方法用于减少强引用计数,并在引用计数降为零时释放对象。
inline void ref_count_sp::release()
{
EASTL_ASSERT((mRefCount.load(memory_order_relaxed) > 0));
if(mRefCount.fetch_sub(1, memory_order_release) == 1)
{
atomic_thread_fence(memory_order_acquire);
free_value();//释放对象
}
weak_release();
}
● fetch_sub:原子操作,减少引用计数并返回旧值。
● free_value():虚函数,在引用计数降为零时调用,实际执行的是派生类中的实现。
派生类 ref_count_sp_t 实现了具体的删除逻辑:
template <typename T, typename Allocator, typename Deleter>
class ref_count_sp_t : public ref_count_sp {
public:
typedef ref_count_sp_t<T, Allocator, Deleter> this_type;
typedef T value_type;
typedef Allocator allocator_type;
typedef Deleter deleter_type;
value_type mValue; // This is expected to be a pointer.
deleter_type mDeleter;
allocator_type mAllocator;
ref_count_sp_t(value_type value, deleter_type deleter, allocator_type allocator)
: ref_count_sp(), mValue(value), mDeleter(eastl::move(deleter)), mAllocator(eastl::move(allocator))
{}
void free_value() EA_NOEXCEPT override {
mDeleter(mValue);
mValue = nullptr;
}
void free_ref_count_sp() EA_NOEXCEPT override {
allocator_type allocator;
allocator.destroy(this);
allocator.deallocate(this, 1);
}
};
● free_value():重写了基类的虚函数,使用 mDeleter 删除对象。
● free_ref_count_sp():重写了基类的虚函数,释放 ref_count_sp_t 对象本身。
shared_ptr 类封装了上述引用计数管理机制,并提供了用户接口。
template <typename T>
class shared_ptr
{
public:
typedef shared_ptr<T> this_type;
typedef T element_type;
typedef typename shared_ptr_traits<T>::reference_type reference_type; // This defines what a reference to a T is. It's always simply T&, except for the case where T is void, whereby the reference is also just void.
typedef EASTLAllocatorType default_allocator_type;
typedef default_delete<T> default_deleter_type;
typedef weak_ptr<T> weak_type;
protected:
element_type* mpValue;
ref_count_sp* mpRefCount; /// Base pointer to Reference count for owned pointer and the owned pointer.
}
析构过程
~shared_ptr()
{
if (mpRefCount)
{
mpRefCount->release();
}
}
析构时调用 mpRefCount->release() 判断引用计数是否为0:
inline void ref_count_sp::release()
{
EASTL_ASSERT((mRefCount.load(memory_order_relaxed) > 0));
if(mRefCount.fetch_sub(1, memory_order_release) == 1)
{
atomic_thread_fence(memory_order_acquire);
free_value();
}
weak_release();
}
如果为0,调用 free_value() , free_value()调用删除器的 ()运算符重载
最终删除对象指针.
void free_value() EA_NOEXCEPT
{
mDeleter(mValue);
mValue = nullptr;
}
//----------//
deleter_type mDeleter;
//mDeleter 实际上就是default_delete
template <typename T>
struct default_delete
{
#if defined(EA_COMPILER_GNUC) && (EA_COMPILER_VERSION <= 4006) // GCC prior to 4.7 has a bug with noexcept here.
EA_CONSTEXPR default_delete() = default;
#else
EA_CONSTEXPR default_delete() EA_NOEXCEPT = default;
#endif
template <typename U> // Enable if T* can be constructed with U* (i.e. U* is convertible to T*).
default_delete(const default_delete<U>&, typename eastl::enable_if<is_convertible<U*, T*>::value>::type* = 0) EA_NOEXCEPT {}
void operator()(T* p) const EA_NOEXCEPT
{
static_assert(eastl::internal::is_complete_type_v<T>, "Attempting to call the destructor of an incomplete type");
delete p;
}
};
使用示例:
class Object
{
public:
int A = 0;
Object(int v) : A(v)
{
cout<<"CreateObj " << A <<"\n";
}
~Object()
{
cout<<"DestroyObj " << A <<"\n";
}
};
int main()
{
{
using ObjectPtr = eastl::shared_ptr<Object>;
Object* Object1 = new Object(1);
ObjectPtr ptr1(Object1);
//仍然可以通过原始指针访问 Object1
//cout << format("Object1 value: {}\n", Object1->A);
//---------------------------------------------------//
cout << format("ptr1 count: {} value:{} \n", ptr1.use_count(),ptr1->A);
//ptr1 count: 1 value:1
ObjectPtr ptr2(ptr1);
ptr2->A = 5;
cout << format("ptr1 count: {} value:{} \n", ptr1.use_count(),ptr1->A);
//ptr1 count: 2 value:5
cout << format("ptr2 count: {} value:{} \n", ptr1.use_count(),ptr2->A);
//ptr2 count: 2 value:5
{
ObjectPtr ptr3(ptr2);
cout << format("ptr1 count: {} ptr2 count: {} \n", ptr1.use_count(),ptr2.use_count());
//ptr1 count: 3 ptr2 count: 3
ObjectPtr ptr4(ptr3);
cout << format("ptr3 count: {} ptr4 count: {} \n", ptr3.use_count(),ptr4.use_count());
//ptr3 count: 4 ptr4 count: 4
ObjectPtr ptr5(ptr4);
ObjectPtr ptr6(ptr5);
cout << format("ptr5 count: {} ptr6 count: {} \n", ptr5.use_count(),ptr6.use_count());
//ptr5 count: 6 ptr6 count: 6
}
cout << format("ptr1 count: {} \n", ptr1.use_count());
//ptr1 count: 2
cout <<"---------- End --------------\n";
}
//DestroyObj 5
while(true){}
}
输出:
CreateObj 1
ptr1 count: 1 value:1
ptr1 count: 2 value:5
ptr2 count: 2 value:5
ptr1 count: 3 ptr2 count: 3
ptr3 count: 4 ptr4 count: 4
ptr5 count: 6 ptr6 count: 6
ptr1 count: 2
---------- End --------------
DestroyObj 5
safe_ptr
safe_ptr 是一种自动且轻量的解决方案,用于解决悬空指针问题。
类描述
这个类是 weak_ptr 的一种替代方案,其主要优点是不需要分配额外的内存,但代价是稍微慢一些,并且不是线程安全的。
在正常使用情况下,safe_ptr
工作原理
这一机制通过让原始对象继承自 safe_object 类来实现。safe_object 类维护一个指向它的 SafePtrs 的链表。当一个 safe_object 被销毁时 会遍历其链表,将每个 SafePtr 的对象引用设置为 NULL。
这种开销很小——只需在所指向的对象大小中增加一个指针,并且 safePtr 的大小等于一个原始指针加上一个链表指针。
线程安全性
这个类不是线程安全的。特别是,操作引用同一个底层对象的 safe_ptr 对象不能从多个线程安全地进行。然而,不相关的 safe_ptr 对象可以在多个线程中安全使用。
class RandomLifetimeObject : public eastl::safe_object
{
public:
int A = 0;
RandomLifetimeObject(int value) : A(value){}
void Print()
{
cout << "A: " << A <<"\n";
}
};
int main()
{
{
Object* Object1 = new Object(1);
eastl::safe_ptr<RandomLifetimeObject> pSafePtr(new RandomLifetimeObject(50));
pSafePtr->Print();
eastl::safe_ptr<RandomLifetimeObject> pSafePtrCopy = pSafePtr;
pSafePtrCopy->Print();
pSafePtr->Print();
delete pSafePtr;
// 此时,pSafePtrCopy 评估为 NULL。
//get()返回false
if (pSafePtrCopy.get())
{
pSafePtrCopy->Print();
}
else
{
cout << "pSafePtrCopy is null\n";
}
}
while (true)
{
}
}

浙公网安备 33010602011771号