【转载】UE4 智能指针学习记录

576 阅读9分钟

版权声明:本文为CSDN博主「YakSue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/u013412391/…

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