UE4智慧指針學習記錄

2020-08-08 20:48:25

UE4智慧指針

之前的部落格《實現一個最基礎的智慧指針》裏實現了一個最簡單的使用參照計數方式的智慧指針。而UE4也有一套智慧指針的實現,可見《虛幻智慧指針庫 | Unreal Engine Documentation》中的介紹。

另外,我發現\Engine\Source\Runtime\Core\Public\Templates\SharedPointer.h中的註釋也有很多有價值的資訊,例如設計意圖等,可以補充參考。(由於有些內容還沒有較深的理解,不敢妄加翻譯,先保留原文)

註釋中的資訊

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.

這個智慧指針庫包括:「共用參照(TSharedRef)」「共用指針(TSharedPtr)」「弱指針(TWeakPtr)」 以及相關的助手函數和助手類。這個庫是在C++0x標準庫的shared_ptr以及Boost的智慧指針之後實現的。

使用智慧指針益處

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.)

  • 乾淨的語法結構。你可以「拷貝」,「解除」,「比較」一個共用指針,語法上就和常規的C++指針一樣。
  • 避免記憶體漏失。當沒有其他參照時資源就會被自動銷燬。
  • 弱參照:弱指針可以讓你安全地檢查一個物件是否被銷燬了。
  • 執行緒安全:包含一個「執行緒安全型版本」可以安全地在多執行緒中存取。
  • Ubiquitous. You can create shared pointers to virtually any type of object.
  • 執行時安全性:共用參照永遠不會爲空,而且總是可以解除參照。
  • 避免回圈參照:使用弱指針可以破壞參照回圈。
  • Confers intent. You can easily tell an object owner from an observer.
  • 效能:共用指針都儘量使用了最小的封裝,所有的操作都是「常數時間複雜度」。
  • 魯棒性相關的特性:支援「const」,前向宣告,型別轉換等。
  • 記憶體:在64位元上僅僅是C++指針的兩倍(外加一個16位元的參照計數器)

這個庫包含了下面 下麪智慧指針的定義

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

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

  • 如果能用TSharedRef就不用TSharedPtr,因爲TSharedRef可以保證值不是空的。
  • 你可以呼叫TSharedPtr::Reset()來解除對物體的參照(可能會導致物體的解構)。
  • 使用MakeShareable()助手函數來隱式地將指針轉換爲TSharedRefTSharedPtr
  • 你永遠不能將TSharedRef重置爲空,但你可以將它指定爲一個新的物件。
  • 共用指針已經假定了對物體的所有權——因此就不需要再手動呼叫delete了。
  • 通常,你應該使用new來將一個C++指針傳遞給一個新的智慧指針。
  • 當要向函數傳遞參數時,使用TSharedRefTSharedPtr,而不是TWeakPtr
  • 「執行緒安全型」版本稍微慢一點——因此請僅在需要的時候才使用。
  • 你可以在智慧指針中使用前向宣告未完成的類。
  • Shared pointers of compatible types will be converted implicitly (e.g. upcasting)
  • 你可以對TSharedRef< MyClass >使用typedef來讓表達更簡潔。
  • 處於效能考慮,minimize calls to TWeakPtr::Pin (or conversions to TSharedRef/TSharedPtr)
  • 讓你的類繼承TSharedFromThis,你就可以return itself as a shared reference。
  • 爲了向下轉換成一個派生類的指針,需要用StaticCastSharedPtr函數。
  • const 是完全支援的!
  • 你可以用ConstCastSharedPtr函數來讓一個const共用指針mutable

限制

-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

  • 共用指針不支援UObject物件
  • 當前只支援那些有常規解構函式的類(沒有自定義的deleter
  • 動態分配記憶體的陣列是不支援的(例如: MakeSharable( new int32[20] )是不對的)
  • TSharedPtrTSharedRef轉換爲bool是不支援的。

和其他實現的區別(例如 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

  • 類名和操作名與UE4的程式碼更一致。
  • 你必須使用Pin()操作來將一個弱指針轉換爲一個共用指針(沒有顯式的建構函式)。
  • 「執行緒安全型」這個特性是可選的,而不是強制的。
  • TSharedFromThis返回的是一個共用參照,而不是共用指針。
  • 有一些操作被忽略了(例如 use_count(),unique()等)
  • 不允許有exceptions(相關的操作都已經被忽略)。
  • 自定義的分配器以及自定義的delete函數都還不支援。
  • 我們的實現包含了一個非空的智慧指針(指TSharedRef
  • 增加了一些新的特性:例如MakeShareable和賦空值。

爲什麼要實現我們自己的虛幻智慧指針?

-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

  • std::shared_ptr(甚至是tr1::shared_ptr)都還沒有在所有平臺上都可用。
  • 在所有平臺和編譯器上有一個更加一致的實現。
  • 可以無縫與UE4的容器類等類對接。
  • 能針對平臺特性(指執行緒,優化,等)有更好的控制。
  • 我們想要「執行緒安全型」這一特性是可選的(處於效能方面考慮)
  • 我們新增了我們自己的改進 ,例如MakeShareable和賦空值。
  • 在我們的實現中,exceptions是不需要且不被期望的。
  • 我們想要對效能有更好的控制(內聯,記憶體,virtual的使用,等)
  • 潛在更容易偵錯 (liberal code comments, 等)
  • 我們傾向於不使用新的第三方依賴,除非真的需要。

使用 TSharedPtr

使用TSharedPtr的目的,正如之前在《實現一個最基礎的智慧指針》所討論的,是想要用參照計數的方式來維護一個物件的銷燬。

class FTestClass
{
};

可以使用MakeShareable將一個普通C++指針轉換爲智慧指針,這將意味着它指向的物件將會被智慧指針的機制 機製管理,在參照計數爲0時自動銷燬。

TSharedPtr<FTestClass> test = MakeShareable(new FTestClass());

需要注意的是,TSharedPtr不能對UObject類使用,因爲UObject自己已經有GC的機制 機製了,不能再將其加入另一個記憶體管理的機制 機製。

使用 TWeakObjectPtr

雖然上面討論了弱指針TWeakPtr,但我目前還沒有使用過它。

不過另外一種「弱指針」倒是經常使用:TWeakObjectPtr
使用它的目的是:有時候不確定一個UObject是否已經被某種原因被GC掉了,將其包裹進TWeakObjectPtr,則可用IsValid方法來確定它是否還有效。

/**
 * FWeakObjectPtr is a weak pointer to a UObject. 
 * It can return nullptr later if the object is garbage collected.
 * It has no impact on if the object is garbage collected or not.
 * It can't be directly used across a network.
 *
 * Most often it is used when you explicitly do NOT want to prevent something from being garbage collected.
 */
struct FWeakObjectPtr
  • FWeakObjectPtr是一個針對UObject的弱指針
  • 若物件已經被GC,則它可以返回nullptr
  • 它對於一個物件是否被GC並沒有任何影響
  • 不能直接在網路中使用
  • 最常用的情況是:你並不想阻止一個物體被GC(但是還想要檢測它是否被GC了)。
/**
 * TWeakObjectPtr is the templated version of the generic FWeakObjectPtr
 */
template<class T, class TWeakObjectPtrBase>

TWeakObjectPtrBase就是FWeakObjectPtr的模板版本。

/**  
 * Test if this points to a live UObject
 * @param bEvenIfPendingKill if this is true, pendingkill objects are considered valid
 * @param bThreadsafeTest if true then function will just give you information whether referenced
 *							UObject is gone forever (return false) or if it is still there (return true, no object flags checked).
 * @return true if Get() would return a valid non-null pointer
 */
FORCEINLINE bool IsValid(bool bEvenIfPendingKill, bool bThreadsafeTest = false) const
{
	return TWeakObjectPtrBase::IsValid(bEvenIfPendingKill, bThreadsafeTest);
}

/**
 * Test if this points to a live UObject. This is an optimized version implying bEvenIfPendingKill=false, bThreadsafeTest=false.
 * @return true if Get() would return a valid non-null pointer
 */
FORCEINLINE bool IsValid(/*bool bEvenIfPendingKill = false, bool bThreadsafeTest = false*/) const
{
	return TWeakObjectPtrBase::IsValid();
}
  • IsValid可以檢測一個UObject是否「活着」
  • 參數bEvenIfPendingKill:如果是true,則pendingkill的物件將被視爲有效,預設false。
  • 參數bThreadsafeTest:if true then function will just give you information whether referenced UObject is gone forever (return false) or if it is still there
  • 返回true則表示Get()能得到一個非空且有效的指針。

當然,TWeakObjectPtr只能和UObject類配合,否則便會報錯:

error C2338: TWeakObjectPtr can only be constructed with UObject types