UE与STL智能指针
参考链接:C++ STL 四种智能指针
浅析UE5中的智能指针源码(上)
浅析UE5中的智能指针源码(下)
1. STL智能指针
C++ 标准模板库 STL一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针,其中shared_ptr作为标准的共享所有权得智能指针最为常用。STL智能指针一般可以有效防止内存泄漏,但不是线程安全的
1.1 unique_ptr
unique_ptr 是 C++11 新增的智能指针,它是一种独占式智能指针,它禁止其他智能指针与其共享同一对象,从而保证代码的安全性,如果出现了共享所有权的情况可能会编译出错。unique_ptr定义在头文件中,无法复制到其他的unique_ptr,无法通过值传递给函数,也无法用于需要副本得STL算法,但是可以通过std::move转移所有权。unique_ptr可以改变指向的对象,也可以动态释放或者转移所有权.其主要的操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| unique_ptr<T> u1; u1.reset(new T()); unique_ptr<T> u2(new T()); unique_ptr<T,D> u(d); //创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 auto u = make_unique<T>();
T* p = u1.release(); unique_ptr<T> u2; u2 = std::move(u1); u2.reset(u1.release()); u1.reset(); u1=nullptr;
|
1.2 shared_ptr
shared_ptr是一个标准的共享智能指针,同样定义在头文件中,通过引用计数的方式来管理资源,当引用计数为0时,自动释放资源,可以自定义释放的规则.shared_ptr的引用计数主要通过专门的控制块实现,其中包含应用计数和weak_ptr使用的weak count.除此之外还有指向资源的指针,自定义的deleter,allocator等.通过make_shared函数创建,以及通过原始指针和unique_ptr创建的shared_ptr会直接创建新的控制块,而通过拷贝或者直接赋值则不会产生新的控制块.
shared_ptr主要的操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| shared_ptr<T> sp; shared_ptr<T> sp(p); unique_ptr<T> u(new T()); shared_ptr<T> sp(u); shared_ptr<T> sp2(sp); shared_ptr sp = std::make_shared<T>(); shared_ptr sp = std::shared_from_this();
shared_ptr<T> sp1(new T()); shared_ptr<T> sp2(new T()); sp2 = sp1; sp1.reset(); sp1.reset(new T());
shared_ptr<base> spbase(new derived()); shared_ptr<derived> spderived(new derived()); shared_ptr<base> spbase2(dynamic_pointer_cast<base>(spderived));
T* p = new T(); shared_ptr<T> sp1(p); shared_ptr<T> sp2(p);
|
1.3 weak_ptr
shared_ptr的循环引用问题:虽然shared_ptr通过引用计数能够有效共享资源以及防止内存泄漏,但是还是存在循环引用的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class A {
int dataA_; std::share<B> ptrb_; };
class B { int dataB_ std::share<A> ptra_; };
int main{ std::share_ptr<A> ptra=std::make_share<A>(); std::share_ptr<B> ptrb=std::make_share<B>();
ptra->ptrb_ =ptrb; ptrb->ptra_ =ptra; }
|
在退出main函数时,两个对象的引用计数都不为0,因此两个对象都不会被释放,导致内存泄漏,这就是shared_ptr循环引用的问题.这里类成员变量的shared_ptr如果换成weak_ptr,则不会出现这个问题.
weak_ptr是一种弱引用的智能指针,可以解决shared_ptr的循环引用问题.它可以指向shared_ptr所指向的对象,但是不会增加引用计数,当shared_ptr的引用计数为0时,即使weak_ptr还指向该对象,该对象也会被释放.weak_ptr不具有普通指针的行为,没有重载operator*和operator->,因此只能观测资源的使用情况.weak_ptr同样采用引用计数的方式,
weak_ptr的主要的操作如下:
1 2 3 4 5 6 7 8
| weak_ptr<T>w; weak_ptr<T> w(sp); w=p; w.reset(); w.use_count(); w.expired(); w.lock();
|
2. UE5智能指针
UE引擎在STL智能指针的基础上,重新搞了一套智能指针,其与STL智能指针的对标关系为:TSharedPtr对应shared_ptr,TWeakPtr对应weak_ptr,TUniquePtr对应unique_ptr,TSharedFromThis对应enable_shared_from_this.除此之外,UE5还提供了独有的TSharedRef.在UE中智能指针只能用于C++,不能共享到蓝图中.除了这些指针,针对资源加载的控制,UE5还提供了FSoftObjectPtr,FSoftClassPtr,FSoftClassPath,FSoftObjectPath等引用.针对UObject类别系统,UE提供了FObjectPtr/TObjectPtr,一般在使用需要进行访问追踪的UPROPERTY的成员变量时使用,而函数参数或者局部参数直接使用UObject*裸指针即可.
- STL的智能指针无法做到全平台可用,因此UE5提供了自己的智能指针;
- UE的智能指针可以兼容UE提供的容器,同时也可以切换成线程安全模式;
- UE智能指针拥有更好的性能,占用跟小的空间,同时也更容易调试;
- UE智能指针目前没有自定义删除函数,也无法支持动态分配的数组,共享的指针不能和uobject一起使用;
- 应该尽量少将弱指针转化为共享指针或者共享引用的操作,会对性能有较大的影响.
2.1 TSharedRef
共享应用是一类不可为空的智能指针,一般用于Uobject系统之外的数据对象,因此无法重置或者向其指定空对象,或者创建空白引用.在与TSharedPtr之间进行选择时,除非需要空白应用或者指向空对象,否则都应该优先使用TSharedRef.同时,共享指针也无法主动减少引用数,只能通过生命周期终结或者指向其他共享引用减少引用数.其基础使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| TSharedRef<FMyObjectType> ref = MakeShared(new T()); TSharedRef< float, Mode > FloatRef( new float( 123.0f )); TSharedRef<FMyObjectType> ref2 = ref; TSharedRef<FMyObjectType> ref3 = MoveTemp(ref);
TSharedRef<FMyObjectType> UnassignedReference; TSharedRef<FMyObjectType> NullAssignedReference = nullptr;
TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference; TSharedRef<FMyObjectType>MySharedReference = MySharedPointer.ToSharedRef();
const float& MyFloat = *FloatRef; const float& MyFloat2 = FloatRef.Get(); TWeakPtr< float, Mode > WeakFloat = FloatRef;
|
2.2 TUniquePtr
唯一指针,不能将其赋值给不能为共享引用或者共享指针指向的对象创建唯一指针.TUniquePtr的基本使用如下:
1 2 3 4 5 6 7 8 9 10 11
| TUniquePtr<MyObject> ObjUniquePtr = MakeUnique<MyObject>(); TUniquePtr<MyObject> ObjUniquePtr2(new MyObject());
ObjUniquePtr.IsValid(); ObjUniquePtr.Get(); ObjUniquePtr.Reset(); ObjUniquePtr.Release(); TUniquePtr<SimpleObject> ObjUniquePtr2(ObjUniquePtr.Release()); ObjUniquePtr.Reset(new SimpleObject()); ObjUniquePtr.ExeFun();
|
2.3 TWeakPtr&TWeakObjectPtr
弱指针存储对资源的弱引用,同时不会阻止其引用的对象的释放.由于Uobject使用的是GC机制,而共享指针使用的是引用计数,因此对于UObject和其他类只能定义两种弱指针.TWeakptr是一种弱引用的智能指针,只能用于UObject之外的对象.TWeakObjectPtr是TWeakPtr的特化版本,用于UObject对象,弱引用可以在忽略一个对象是否有效的情况下直接使用该对象.常用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| TWeakPtr<MyObject> ObjWeakPtr = ObjUniquePtr; TSharedRef<FMyObjectType> ObjectOwner = MakeShared<FMyObjectType>(); TWeakPtr<FMyObjectType> ObjectObserver(ObjectOwner); TWeakPtr<FMyObjectType> ObjectObserver2 = ObjectOwner;
ObjectObserver.pin(); ObjectObserver.IsValid(); ObjectObserver.Reset(); ObjectObserver = nullptr;
TWeakObjectPtr<AActor> ActorWeakPtr = nullptr; TWeakObjectPtr<const AActor> ActorWeakPtr2 = this;
|
2.4 TSharedPtr
可为空指针的鲁棒共享智能指针,可以用于UObject之外的对象,也可以用于UObject对象,但是不建议这么做,因为UObject对象使用GC机制,而共享指针使用引用计数机制,两者不兼容,会导致内存泄漏.常用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType()); TSharedPtr<FMyObjectType, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FMyObjectType, ESPMode::ThreadSafe>(MyArgs); TSharedPtr<FMyObjectType> AnotherPointer = ExistingSharedPointer;
SharedPointer.IsValid(); SharedPointer.Reset(); SharedPointer = nullptr;
TSharedPtr<FMyObjectType> PointerTwo = MoveTemp(PointerOne);
SharedPointer->Function(); (*SharedPointer).Function(); SharedPointer.Get()->Function();
|
2.5 TSharedFromThis
使用this指针构造返回一个共享指针,与STL中的enable_shared_from_this非常相似,这里对其就不做过多的赘述了.
3. 结语
STL的智能指针为C++的内存泄漏问题引入了一个初步的解决方案,而UE中针对游戏开发下的不同使用场景以及能否兼容UObject的GC机制重新增加了不少功能.如果单纯知道这些指针的功能还是远远不够的,还是希望以后能有机会解析一下UE智能指针的源码吧.