版权声明:本文为CSDN博主「YakSue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
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()
助手函数来隐式地将指针转换为TSharedRef
或TSharedPtr
。 - 你永远不能将
TSharedRef
重置为空,但你可以将它指定为一个新的对象。 - 共享指针已经假定了对物体的所有权——因此就不需要再手动调用
delete
了。 - 通常,你应该使用
new
来将一个 C++ 指针传递给一个新的智能指针。 - 当要向函数传递参数时,使用
TSharedRef
或TSharedPtr
,而不是TWeakPtr
。 - “线程安全型” 版本稍微慢一点——因此请仅在需要的时候才使用。
- 你可以在智能指针中使用前向声明未完成的类。
- Shared pointers of compatible types will be converted implicitly (e.g. upcasting)
- 你可以对
TSharedRef< MyClass >
使用typedef
来让表达更简洁。 - 处于性能考虑,minimize calls to
TWeakPtr::Pin
(or conversions toTSharedRef
/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] )
是不对的) - 将
TSharedPtr
或TSharedRef
转换为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