前言

C++类指针

介绍了
TObjectPtr TWeakObjectPtr TSoftObjectPtr TStrongObjectPtr

TSharedPtr TWeakPtr TUniquePtr TSharedRef

我来为您详细介绍虚幻引擎(Unreal Engine)中的这四个智能指针类型。这些是UE4/UE5 C++编程中核心的内存管理工具。

  1. TSharedPtr(共享指针)

核心特性:引用计数、共享所有权

1
2
3
4
5
6
// 创建方式
TSharedPtr<FMyClass> SharedPtr = MakeShared<FMyClass>(); // 推荐
TSharedPtr<FMyClass> SharedPtr2(new FMyClass()); // 也可行但不推荐

// 复制时引用计数+1
TSharedPtr<FMyClass> AnotherPtr = SharedPtr; // 计数=2

特性 说明
线程安全 是,引用计数操作为原子操作
允许空值 是,IsValid()Get() 检查
性能开销 轻微(引用计数维护)
析构时机 最后一个引用离开作用域时


  1. TWeakPtr(弱指针)

核心特性:不增加引用计数、解决循环引用

1
2
3
4
5
6
7
8
9
10
11
12
13
TSharedPtr<FMyClass> Shared = MakeShared<FMyClass>();
TWeakPtr<FMyClass> Weak = Shared; // 不增加引用计数

// 使用前必须提升(Promote)为TSharedPtr
if (TSharedPtr<FMyClass> Pinned = Weak.Pin())
{
// 对象仍然存在,安全使用
Pinned->DoSomething();
}
else
{
// 对象已被销毁
}

典型应用场景:

  • 观察者模式中的观察者(避免循环引用)
  • 缓存对象,不阻止其被垃圾回收
  • UI系统中对控件的引用

  1. TUniquePtr(唯一指针)

核心特性:独占所有权、不可复制、可移动

1
2
3
4
5
6
7
8
9
10
11
12
// 创建
TUniquePtr<FMyClass> Unique = MakeUnique<FMyClass>();

// 错误:不可复制
// TUniquePtr<FMyClass> Copy = Unique; // 编译错误!

// 正确:可以移动
TUniquePtr<FMyClass> Moved = MoveTemp(Unique);
// 现在 Unique 为 nullptr,Moved 拥有所有权

// 释放所有权
FMyClass* Raw = Unique.Release(); // 手动管理,需自行delete

对比 TUniquePtr std::unique_ptr
接口 类似 几乎一致
自定义删除器 支持 支持
性能 零开销 零开销


  1. TSharedRef(共享引用)

核心特性:非空保证、共享所有权

1
2
3
4
5
6
7
8
9
// 创建 - 不能为空,必须立即初始化
TSharedRef<FMyClass> Ref = MakeShared<FMyClass>();

// 从TSharedPtr转换(必须确保非空)
TSharedPtr<FMyClass> Ptr = MakeShared<FMyClass>();
TSharedRef<FMyClass> Ref2 = Ptr.ToSharedRef(); // 断言检查非空

// 作为函数参数 - 明确表示"需要一个有效对象"
void ProcessData(TSharedRef<FData> Data);

与 TSharedPtr 的关键区别:

特性 TSharedRef TSharedPtr
能否为null 否 是
默认构造 不存在 存在(null)
使用场景 参数传递,确保有效性 通用场景,允许空值


对比总结

指针类型 所有权 空值允许 引用计数 主要用途
TSharedPtr 共享 ✅ ✅ 通用共享所有权
TWeakPtr 无 ✅ ❌ 打破循环引用,观察
TUniquePtr 独占 ✅ ❌ 明确唯一所有权,RAII
TSharedRef 共享 ❌ ✅ 非空约束的共享所有权


最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 优先使用 MakeShared/MakeUnique
auto Ptr = MakeShared<FClass>(); // 比 new 更高效(单次分配)

// 2. 函数参数选择
void ReadData(TSharedRef<FData> Data); // 必须有效
void ReadData(TSharedPtr<FData> Data); // 可能为空
void ReadData(const FData& Data); // 原始引用,无所有权

// 3. 避免循环引用
class A {
TWeakPtr<B> Other; // 使用弱指针,而非 TSharedPtr
};

这些智能指针是虚幻引擎内存管理的基石,理解它们的差异对于编写健壮的UE C++代码至关重要。

\1. UE 智能指针类型

  • **TSharedPtr**:强引用指针,拥有对对象的强引用。只要有一个 TSharedPtr 指向对象,对象就不会被销毁。
  • **TWeakPtr**:弱引用指针,不拥有对象的引用计数。它不会阻止对象的销毁,但可以安全地检查对象是否仍然存在。
  • **TUniquePtr**:独占指针,表示唯一所有权。当 TUniquePtr 超出作用域时,它所指向的对象会被自动销毁。

\2. 何时使用强引用(TSharedPtr

强引用(TSharedPtr)适用于以下场景:

  • 需要明确管理对象的生命周期:当你希望对象的生命周期由引用计数决定时,使用 TSharedPtr
  • 共享所有权:当多个对象需要共享对同一资源的访问权时,使用 TSharedPtr
  • 避免悬空指针:当你需要确保对象在使用时不会被意外销毁时,使用 TSharedPtr

示例:

1
2
// 只要 SharedObject 存在,MyObject 就不会被销毁
TSharedPtr<MyObject> SharedObject = MakeShared<MyObject>();

3. 何时使用弱引用(TWeakPtr

弱引用(TWeakPtr)适用于以下场景:

  • **避免循环引用**:当两个对象相互持有对方的强引用时,会导致循环引用,内存无法释放。此时可以使用 TWeakPtr 打破循环。
  • 观察者模式:当你需要观察一个对象,但不想影响其生命周期时,使用 TWeakPtr
  • 临时访问对象:当你需要临时访问一个对象,但不希望阻止其销毁时,使用 TWeakPtr

示例:

1
2
3
4
5
6
7
8
9
 TSharedPtr<MyObject> SharedObject = MakeShared<MyObject>(); TWeakPtr<MyObject> WeakObject = SharedObject;
if (TSharedPtr<MyObject> PinnedObject = WeakObject.Pin())
{
// 对象仍然存在,可以安全使用
}
else
{
// 对象已被销毁
}

4. 何时使用 TUniquePtr

TUniquePtr 适用于以下场景:

  • 独占所有权:当对象只有一个所有者时,使用 TUniquePtr
  • 明确的生命周期管理:当你希望对象的生命周期与作用域绑定,超出作用域时自动销毁。

示例:

1
2
// UniqueObject 超出作用域时,MyObject 会被自动销毁
TUniquePtr<MyObject> UniqueObject = MakeUnique<MyObject>();

5. 强引用 vs 弱引用的选择

  • 强引用

    • 当你需要确保对象在使用期间不会被销毁时使用。
    • 会增加引用计数,影响对象的生命周期。
    • 可能导致循环引用问题。
  • 弱引用

    • 当你需要观察对象但不影响其生命周期时使用。
    • 不会增加引用计数,不会阻止对象销毁。
    • 需要在使用前检查对象是否仍然存在。

6. 避免循环引用

循环引用是使用智能指针时常见的问题。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A 
{
public:
TSharedPtr<B> BInstance;
};

class B
{
public: TSharedPtr<A> AInstance;
};

TSharedPtr<A> AObj = MakeShared<A>();
TSharedPtr<B> BObj = MakeShared<B>();
AObj->BInstance = BObj;
BObj->AInstance = AObj; // 循环引用,内存泄漏

解决方法是将其中一个指针改为弱引用:

1
2
3
4
class B 
{
public: TWeakPtr<A> AInstance; // 使用弱引用打破循环
};

7. 总结

  • 强引用(TSharedPtr:用于需要明确管理对象生命周期的场景。
  • 弱引用(TWeakPtr:用于避免循环引用或观察对象但不影响其生命周期的场景。
  • 独占指针(TUniquePtr:用于独占所有权的场景。

根据具体需求选择合适的智能指针类型,可以有效管理内存并避免常见问题(如内存泄漏或悬空指针)。

UObject类指针

TStrongObjectPtr,TWeakObjectPtr,FSoftObjectPtr,TSoftObjectPtr,TSoftClassPtr,FSoftObjectPath,FLazyObjectPtr,TPersistentObjectPtr,FGCObject,FGCObjectScopeGuard,TGCObjectsScopeGuard,TWeakInterfacePtr

参考

(99+ 封私信 / 80 条消息) 虚幻引擎随笔:虚幻引擎智能指针 - 知乎

(99+ 封私信 / 80 条消息) UE4的智能指针 UObject相关 - 知乎