【UE4 C++ 基礎知識】<15> 智能指針 TSharedPtr、UniquePtr、TWeakPtr、TSharedRef


基本概念

  • 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;
    
    

    image

復制/轉移

  • 賦值

  • 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()
    

    image

條件判斷 / 對比 / 解引用與訪問

  • -> 運算符

  • 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"));
    }
    

    image


共享引用 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());
    }
    

    image


弱指針 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;
    

    image


唯一指針 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;
    

    image


基類與派生類的智能轉換

共享指針轉換

  • 派生類轉基類 隱式轉換

  • 基類轉派生類 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"));
    

image

共享引用轉換

  • 隱式轉換
  • StaticCastSharedRef

// 創建唯一指針
TUniquePtr ObjUniquePtr = MakeUnique ();
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);
    

    image


注意

  • 避免將數據作為 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
 *
 */

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM