基本概念
- UE4 對 UObject 對象提供垃圾回收
- UE4 對原生對象不提供垃圾回收,需要手動進行清理
- 方式
- malloc / free
- new / delete
new與malloc的區別在於,new在分配內存完成之后會調用構造函數。
- 缺點
- 如果不及時清理,則會占用內存,或者導致內存泄漏
- 如果不小心提前清理,則會導致野指針
- 方式
- UE4 提供共享指針庫來管理內存,它是C++11智能指針的自定義實現
- 分類
- TSharedPtr
- UniquePtr
- TWeakPtr
- TSharedRef
- 優點
- 防止內存泄漏 共享引用不存在時,智能指針(弱指針除外)會自動刪除對象。
- 弱引用 弱指針會中斷引用循環並阻止懸掛指針。
- 可選擇的線程安全 虛幻智能指針庫包括線程安全代碼,可跨線程管理引用計數。如無需線程安全,可用其換取更好性能。
- 運行時安全 共享引用從不為空,可固定隨時取消引用。
- 授予意圖 可輕松區分對象所有者和觀察者。
- 內存 智能指針在64位下僅為C++指針大小的兩倍(加上共享的16字節引用控制器)。唯一指針除外,其與C++指針大小相同。
- 分類
共享指針 TSharedPtr
- TSharedPtr 不能指向 UObject。如果想要指向UObject,可以使用TWeakObjectPtr
- TSharedPtr 可以對FStructures 使用
創建/初始化/ 重置
-
MakeShareable()/MakeShared<T>()
函數 -
Reset()
函數class SimpleObject { public: SimpleObject() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"SimpleObject Construct")); } ~SimpleObject() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"SimpleObject Destruct")); } void ExeFun() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"Execute")); } };
// 快速創建共享指針 TSharedPtr<SimpleObject> simObjectPtr(new SimpleObject()); // MakeShareable 創建共享指針 TSharedPtr<SimpleObject> simObjectPtr2 = MakeShareable(new SimpleObject()); // 創建線程安全 TSharedPtr<SimpleObject, ESPMode::ThreadSafe> simObjectPtr3 = MakeShareable(new SimpleObject()); // 查看引用計數 UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"引用計數: simObjectPtr[%d], simObjectPtr2[%d], simObjectPtr3[%d] "), simObjectPtr.GetSharedReferenceCount(), simObjectPtr2.GetSharedReferenceCount(), simObjectPtr3.GetSharedReferenceCount()); // 重置共享指針 simObjectPtr.Reset(); simObjectPtr2 = nullptr;
復制/轉移
-
賦值
-
MoveTemp / MoveTempIfPossible
// 復制共享指針 TSharedPtr<SimpleObject> simObjectPtr_copy = simObjectPtr; UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"引用計數: simObjectPtr[%d], simObjectPtr_copy[%d],"), simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount()); // 轉移共享指針 TSharedPtr<SimpleObject> simObjectPtr_MoveTemp = MoveTemp(simObjectPtr_copy); // 另 MoveTempIfPossible() UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"引用計數: simObjectPtr[%d], simObjectPtr_copy[%d], simObjectPtr_MoveTemp[%d]"), simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount(), simObjectPtr_MoveTemp.GetSharedReferenceCount()
條件判斷 / 對比 / 解引用與訪問
-
->
運算符 -
Get()
函數 -
IsValid()
函數 -
==
!=
運算符if (simObjectPtr) // 條件判斷 { simObjectPtr->ExeFun(); // 解引用 } if (simObjectPtr.Get() != nullptr) // 條件判斷 { simObjectPtr.Get()->ExeFun(); //解引用 } if (simObjectPtr.IsValid()) // 條件判斷 { (*simObjectPtr).ExeFun(); // 解引用 } if (simObjectPtr == simObjectPtr_copy) // 對比 { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simObjectPtr_copy == simObjectPtr")); }
共享引用 TSharedRef
- 共享引用不可為空
- 不可用於 UObject對象
- 沒有 IsValid() 函數
創建/初始化
-
MakeShareable()/MakeShared<T>()
函數// 創建共享引用 TSharedRef<SimpleObject> objRef(new SimpleObject()); TSharedRef<SimpleObject> objRef2 = MakeShareable(new SimpleObject()); TSharedRef<SimpleObject> objRef3 = MakeShared<SimpleObject>();
TSharedRef 與 TSharedPtr轉換
-
隱式轉化
-
ToSharedRef()
// TSharedRef -> TSharedPtr TSharedPtr<SimpleObject> objPtr = objRef; // TSharedPtr -> TSharedRef objRef3= objPtr.ToSharedRef();
比較
-
沒有 IsValid() 函數
// 共享指針比較 if (objRef == objRef3) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"objRef == objRef3 , 引用計數:%d"), objPtr.GetSharedReferenceCount()); }
弱指針 TWeakPtr
- 與TSharedPtr相比,不參與引用計數
- 對象不存在共享指針時,TWeakPtr將自動失效
- 使用時需要判斷有效性
創建/初始化/轉換/重置
-
通過 TSharedPtr 創建
-
通過 TSharedRef 創建
-
運算符
=
賦值 -
IsValid()
函數判斷有效性 -
Pin()
函數轉成 TSharedPtr ,再解引用訪問對象 -
Reset()
或nullptr
重置// 強指針創建弱指針 TSharedPtr<SimpleObject> ObjPtr=MakeShared<SimpleObject>(); TWeakPtr<SimpleObject> ObjWeakPtr(ObjPtr); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step1 引用計數:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); //強引用創建弱指針 TSharedRef<SimpleObject> objRef = MakeShareable(new SimpleObject()); TWeakPtr<SimpleObject> ObjWeakPtr2(objRef); TWeakPtr<SimpleObject> ObjWeakPtr_Copy = ObjWeakPtr; UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step2 引用計數:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); // 判斷有效性 if (ObjWeakPtr.IsValid()) { TSharedPtr<SimpleObject> ObjPtr2 = ObjWeakPtr.Pin(); ObjPtr2->ExeFun(); } // 清空強指針 ObjPtr.Reset(); TSharedPtr<SimpleObject> ObjPtr2 = ObjWeakPtr.Pin(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step3 引用計數:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); // 判斷有效性 if (!ObjPtr2) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"弱指針已空 ")); } // 重置 ObjWeakPtr.Reset(); ObjWeakPtr_Copy = nullptr;
唯一指針 TUniquePtr
- TUniquePtr 指向的對象只能被唯一指向,因而 Unique指針不能賦值給其它指針
- 不要為共享指針或共享引用引用的對象創建唯一指針
創建/初始化/判斷/解引用/重置
-
MakeUnique
() -
IsValid()
-
->
運算符 -
Get()
函數 -
Release()
釋放並返回指針 -
Reset()
或nullptr
重置// 創建唯一指針 TUniquePtr<SimpleObject> ObjUniquePtr = MakeUnique<SimpleObject>(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d]"), ObjUniquePtr.IsValid()); // 判斷有效性 if (ObjUniquePtr.IsValid()) { ObjUniquePtr->ExeFun(); // 解引用 } // 釋放指針,移交 TUniquePtr<SimpleObject> ObjUniquePtr2(ObjUniquePtr.Release()); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d], ObjUniquePtr2[%d]"), ObjUniquePtr.IsValid(), ObjUniquePtr2.IsValid()); // 重置 ObjUniquePtr.Reset(); ObjUniquePtr2 = nullptr;
基類與派生類的智能轉換
共享指針轉換
-
派生類轉基類 隱式轉換
-
基類轉派生類 StaticCastSharedPtr
-
非常量轉常量 ConstCastSharedPtr
TSharedPtr<SimpleObject> simpleObj; TSharedPtr<ComplexObject> complexObj = MakeShared<ComplexObject>(); // 派生類轉基類 simpleObj = complexObj; UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj is %s"), simpleObj.IsValid() ? TEXT("Valid") : TEXT("Not Valid")); // 基類轉派生類 TSharedPtr<ComplexObject> complexObj2 = StaticCastSharedPtr<ComplexObject>(simpleObj); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"complexObj2 is %s"), complexObj2.IsValid() ? TEXT("Valid") : TEXT("Not Valid")); // 常量指針轉非常量指針 const TSharedPtr<SimpleObject> simpleObj_const(new SimpleObject()); TSharedPtr<SimpleObject> simpleObj_mutable = ConstCastSharedPtr<SimpleObject>(simpleObj_const); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj_mutable is %s"), simpleObj_mutable.IsValid() ? TEXT("Valid") : TEXT("Not Valid"));
共享引用轉換
- 隱式轉換
- StaticCastSharedRef
// 創建唯一指針
TUniquePtr
UE_LOG(LogTemp, Warning, TEXT(
FUNCTION" Validity: ObjUniquePtr[%d]"), ObjUniquePtr.IsValid());
// 判斷有效性
if (ObjUniquePtr.IsValid())
{
ObjUniquePtr->ExeFun(); // 解引用
}
// 釋放指針,移交
TUniquePtr<SimpleObject> ObjUniquePtr2(ObjUniquePtr.Release());
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d], ObjUniquePtr2[%d]"), ObjUniquePtr.IsValid(), ObjUniquePtr2.IsValid());
// 重置
ObjUniquePtr.Reset();
ObjUniquePtr2 = nullptr;
- ConstStaticCastSharedRef
代碼省略
助手類 TSharedFromThis
-
自定義類繼承 TSharedFromThis 模板類
-
TSharedFromThis 會保存一個弱指針
-
AsShared()
將裸指針轉智共享引用,可再隱式轉為共享指針 -
SharedThis(this)
會返回具備"this"類型的TSharedRef -
不要在構造函數中調用 AsShared 或 Shared,共享引用此時並未初始化,將導致崩潰或斷言
// 基類 class BaseClass :public TSharedFromThis<BaseClass> { public: BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); } virtual ~BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); } virtual void ExeFun() { TSharedRef<BaseClass> ThisAsSharedRef = AsShared(); } }; // 派生類 class ChildClass :public BaseClass { public: ChildClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); } virtual ~ChildClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); } virtual void ExeFun() override{ //AsShared()返回 TSharedRef<BaseClass>, 因而編譯不通過 //TSharedRef<ChildClass> AsSharedRef = AsShared(); TSharedRef<ChildClass> AsSharedRef = SharedThis(this); } };
TSharedPtr<BaseClass> BaseClassPtr = MakeShared<BaseClass>(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 引用計數:BaseClassPtr[%d]"), BaseClassPtr.GetSharedReferenceCount()); BaseClass* tempPtr = BaseClassPtr.Get(); TSharedPtr<BaseClass> BaseClassPtr_Shared =tempPtr->AsShared(); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 引用計數:BaseClassPtr[%d], BaseClassPtr_Shared[%d]"), BaseClassPtr.GetSharedReferenceCount(), BaseClassPtr_Shared.GetSharedReferenceCount()); // 使用下面語句運行,程序死機 // TSharedPtr<BaseClass> BaseClassPtr_New = MakeShareable(tempPtr);
注意
- 避免將數據作為 TSharedRef 或 TSharedPtr 參數傳到函數,此操作將因取消引用和引用計數而產生開銷。相反,建議將引用對象作為 const & 進行傳遞。
- 共享指針與虛幻對象(UObject 及其衍生類)不兼容。引擎具有 UObject 管理的單獨內存管理系統(對象處理文檔),兩個系統未互相重疊。
實踐遇到的問題
-
按照附錄源碼頭文件 Tip,智能指針其實可以作為函數參數的
-
智能指針在TArray里排序或者堆操作時,符號重載 operator 要在全局來寫,原類里的貌似不識別
//用於排序比較 bool operator <(const TSharedPtr<FPathPoint>& a,const TSharedPtr<FPathPoint>& b) { return a->total_cost < b->total_cost; } //用於數組里的查找 bool operator ==(const TSharedPtr<FPathPoint>& a,const TSharedPtr<FPathPoint>& b) { return a->pos.Equals(b->pos, 1.0f); }
-
智能指針和原生指針混用,容易撲街,可能會遇到內存釋放等問題
附錄
- \Engine\Source\Runtime\Core\Public\Templates\SharedPointer.h 有更詳細的說明
**
* SharedPointer - Unreal smart pointer library
*
* This is a smart pointer library consisting of shared references (TSharedRef), shared pointers (TSharedPtr),
* weak pointers (TWeakPtr) as well as related helper functions and classes. This implementation is modeled
* after the C++0x standard library's shared_ptr as well as Boost smart pointers.
*
* Benefits of using shared references and pointers:
*
* Clean syntax. You can copy, dereference and compare shared pointers just like regular C++ pointers.
* Prevents memory leaks. Resources are destroyed automatically when there are no more shared references.
* Weak referencing. Weak pointers allow you to safely check when an object has been destroyed.
* Thread safety. Includes "thread safe" version that can be safely accessed from multiple threads.
* Ubiquitous. You can create shared pointers to virtually *any* type of object.
* Runtime safety. Shared references are never null and can always be dereferenced.
* No reference cycles. Use weak pointers to break reference cycles.
* Confers intent. You can easily tell an object *owner* from an *observer*.
* Performance. Shared pointers have minimal overhead. All operations are constant-time.
* Robust features. Supports 'const', forward declarations to incomplete types, type-casting, etc.
* Memory. Only twice the size of a C++ pointer in 64-bit (plus a shared 16-byte reference controller.)
*
*
* This library contains the following smart pointers:
*
* TSharedRef - Non-nullable, reference counted non-intrusive authoritative smart pointer
* TSharedPtr - Reference counted non-intrusive authoritative smart pointer
* TWeakPtr - Reference counted non-intrusive weak pointer reference
*
*
* Additionally, the following helper classes and functions are defined:
*
* MakeShareable() - Used to initialize shared pointers from C++ pointers (enables implicit conversion)
* TSharedFromThis - You can derive your own class from this to acquire a TSharedRef from "this"
* StaticCastSharedRef() - Static cast utility function, typically used to downcast to a derived type.
* ConstCastSharedRef() - Converts a 'const' reference to 'mutable' smart reference
* StaticCastSharedPtr() - Dynamic cast utility function, typically used to downcast to a derived type.
* ConstCastSharedPtr() - Converts a 'const' smart pointer to 'mutable' smart pointer
*
*
* Examples:
* - Please see 'SharedPointerTesting.inl' for various examples of shared pointers and references!
*
*
* Tips:
* - Use TSharedRef instead of TSharedPtr whenever possible -- it can never be nullptr!
* - You can call TSharedPtr::Reset() to release a reference to your object (and potentially deallocate)
* - Use the MakeShareable() helper function to implicitly convert to TSharedRefs or TSharedPtrs
* - You can never reset a TSharedRef or assign it to nullptr, but you can assign it a new object
* - Shared pointers assume ownership of objects -- no need to call delete yourself!
* - Usually you should "operator new" when passing a C++ pointer to a new shared pointer
* - Use TSharedRef or TSharedPtr when passing smart pointers as function parameters, not TWeakPtr
* - The "thread-safe" versions of smart pointers are a bit slower -- only use them when needed
* - You can forward declare shared pointers to incomplete types, just how you'd expect to!
* - Shared pointers of compatible types will be converted implicitly (e.g. upcasting)
* - You can create a typedef to TSharedRef< MyClass > to make it easier to type
* - For best performance, minimize calls to TWeakPtr::Pin (or conversions to TSharedRef/TSharedPtr)
* - Your class can return itself as a shared reference if you derive from TSharedFromThis
* - To downcast a pointer to a derived object class, to the StaticCastSharedPtr function
* - 'const' objects are fully supported with shared pointers!
* - You can make a 'const' shared pointer mutable using the ConstCastSharedPtr function
*
*
* Limitations:
*
* - Shared pointers are not compatible with Unreal objects (UObject classes)!
* - Currently only types with that have regular destructors (no custom deleters)
* - Dynamically-allocated arrays are not supported yet (e.g. MakeSharable( new int32[20] ))
* - Implicit conversion of TSharedPtr/TSharedRef to bool is not supported yet
*
*
* Differences from other implementations (e.g. boost:shared_ptr, std::shared_ptr):
*
* - Type names and method names are more consistent with Unreal's codebase
* - You must use Pin() to convert weak pointers to shared pointers (no explicit constructor)
* - Thread-safety features are optional instead of forced
* - TSharedFromThis returns a shared *reference*, not a shared *pointer*
* - Some features were omitted (e.g. use_count(), unique(), etc.)
* - No exceptions are allowed (all related features have been omitted)
* - Custom allocators and custom delete functions are not supported yet
* - Our implementation supports non-nullable smart pointers (TSharedRef)
* - Several other new features added, such as MakeShareable and nullptr assignment
*
*
* Why did we write our own Unreal shared pointer instead of using available alternatives?
*
* - std::shared_ptr (and even tr1::shared_ptr) is not yet available on all platforms
* - Allows for a more consistent implementation on all compilers and platforms
* - Can work seamlessly with other Unreal containers and types
* - Better control over platform specifics, including threading and optimizations
* - We want thread-safety features to be optional (for performance)
* - We've added our own improvements (MakeShareable, assign to nullptr, etc.)
* - Exceptions were not needed nor desired in our implementation
* - We wanted more control over performance (inlining, memory, use of virtuals, etc.)
* - Potentially easier to debug (liberal code comments, etc.)
* - Prefer not to introduce new third party dependencies when not needed
*
*/