此文章为下面文章的整理,感谢前辈
UE 中的 UObject 本身就可以帮我们做回收,做释放的。我们可以看到 UE4 中所有的类都是继承自UObject,这样的话当我们引擎被关闭的时候,它会帮我们自动释放这些资源,或者是我们手动去释放掉它,这样能保证能彻底删掉它。
UE4 对原生对象(即不继承 UObject 的类)不提供垃圾回收,需要手动进行清理。
原生对象创建和销毁的方式有:malloc / free 和 new / delete。new 与 malloc 的区别在于,new 在分配内存完成之后会调用构造函数。
我们会遇到这样的问题:
为了防止这些问题的出现,智能指针就出现了,它就是用来管理这些非继承 UObject 的类。
UE4 提供共享指针库来管理内存,它是 C++11 智能指针的自定义实现,虚拟智能指针库和C++智能指针在功能和效率方面都有一些共同的特点。
智能指针优点
为什么使用 UE4 的智能指针库?
std::shared_ptr不是在所有的平台都能用的可以查看详细的官方文档。
智能指针的主要类型分为:TSharedPtr, TSharedRef, TWeakPtr, TUniquePtr。
先声明一个测试类
class TestA
{
public:
int32 a;
float b;
};
TSharedPtr 为共享指针。
MakeShareable)/初始化/重置(Reset)用法如下:
void TestSharedPtr()
{
//声明
TSharedPtr MyTestA;
//分配内存
MyTestA = MakeShareable(new TestA());
//先判读智能指针是否有效
if (MyTestA.IsValid() || MyTestA.Get())
{
//访问
int32 a = MyTestA->a;
//复制指针
TSharedPtr MyTesta = MyTestA;
//获取共享指针引用计数
int32 Count = MyTestA.GetSharedReferenceCount();
//销毁对象
MyTesta.Reset();
}
};
TSharedPtr 不能指向 UObject。如果想要指向UObject,可以使用 TWeakObjectPtr。TSharedPtr 可以对 FStructures 使用。
TSharedPtr 可以在 .h 中定义,并且可以置为空(= nullptr)。
TShareRef 不允许在 .h 中定义的,并且一直有值,下面会介绍。
TSharedPtr 的 .Get()方法将 TSharedPtr 转换成 C++ 原生指针。在访问 TSharedPtr 时,需要先通过 .IsValid() 或者 .Get() 判断共享指针是否有效。如果指针是无效的,调用重载的操作符 -> 去获取值时,直接会导致断言(check)失败,将会导致奔溃。
FORCEINLINE
ObjectType* operator->() const
{
// 可以看到这里的check
check( IsValid() );
return Object;
}
注意:MyTestA.IsValid()中 “.” 是访问共享指针的成员,而 MyTestA->a中 “->” 是访问这个指针指向的对象中的成员。
Reset() 用于重置共享指针。
TSharePtr 和 C++11 的 shareptr 一样,内部都是基于引用计数的。所以可以通过GetSharedReferenceCount()获取到当前的引用计数
复制共享指针
// 复制共享指针
TSharedPtr simObjectPtr_copy = simObjectPtr;
UE_LOG(LogTemp, Warning, TEXT("引用计数: simObjectPtr[%d], simObjectPtr_copy[%d],"),
simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount());
输出:
引用计数: simObjectPtr[2], simObjectPtr_copy[2]
转移可以通过 MoveTemp 或 MoveTempIfPossible。
// 转移共享指针
TSharedPtr simObjectPtr_MoveTemp = MoveTemp(simObjectPtr_copy); // 另 MoveTempIfPossible()
UE_LOG(LogTemp, Warning, TEXT("引用计数: simObjectPtr[%d], simObjectPtr_copy[%d], simObjectPtr_MoveTemp[%d]"),
simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount(), simObjectPtr_MoveTemp.GetSharedReferenceCount()
输出:
引用计数: simObjectPtr[2], simObjectPtr_copy[0], simObjectPtr_MoveTemp[2]
-> 运算符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("simObjectPtr_copy == simObjectPtr"));
}
TSharedRef 为共享引用,共享引用不可为空,所以它没有 IsValid 方法,因为它一直是有效的。TSharedRef 不可用于 UObject 对象。
MakeShareable() / MakeShared 函数
void TestSharedRef()
{
// 创建共享引用
TSharedRef objRef(new SimpleObject());
TSharedRef objRef2 = MakeShareable(new SimpleObject());
TSharedRef objRef3 = MakeShared();
//访问:
int32 a = objRef->a;//方法一
int32 b = (*objRef).a;//方法二
//销毁对象
objRef.Reset();
};
注意:
GetSharedReferenceCount() 获取引用技术。TSharedRef 转为 TSharedPtr,支持隐式转换,直接赋值即可。
TSharedPtr 转为 TSharedRef,通过 ToSharedRef()进行转换。
void ATestSharedRefAndPtr()
{
//创建普通指针
TestA* MyTestC = new TestA();
//创建共享指针
TSharedPtr MyTestA;
//创建共享引用
TSharedRef MyTestB(new TestA());
//共享引用转换为共享指针,支持隐式转换
MyTestA = MyTestB;
//普通的指针转换为共享指针
MyTestA = MakeShareable(MyTestC);
//共享指针转换为共享引用,共享指针不能为空
MyTestB = MyTestA.ToSharedRef();
};
TWeakPtr 为弱指针,保持对一个对象的弱引用,与 TSharedPtr 相比,它不参与引用计数,也就不会阻止对象的销毁。当 TWeakPtr 指向的类销毁时,那么这个弱指针(TWeakPtr)也会为null,所以,使用时需要判断有效性。
TWeakPtr 打破 TSharedPtr 共享指针带来的循环引用问题。
TWeakPtr 的创建必须基于一个 TSharedPtr 或者 TSharedRef。
// 强指针创建弱指针
TSharedPtr ObjPtr = MakeShared();
TWeakPtr ObjWeakPtr(ObjPtr);
UE_LOG(LogTemp, Warning, TEXT("step1 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount());
// 强引用创建弱指针
TSharedRef objRef = MakeShareable(new SimpleObject());
TWeakPtr ObjWeakPtr2(objRef);
TWeakPtr ObjWeakPtr_Copy = ObjWeakPtr;
UE_LOG(LogTemp, Warning, TEXT("step2 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount());
// 判断有效性
if (ObjWeakPtr.IsValid())
{
TSharedPtr ObjPtr2 = ObjWeakPtr.Pin();
ObjPtr2->ExeFun();
}
// 清空强指针
ObjPtr.Reset();
UE_LOG(LogTemp, Warning, TEXT("step3 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount());
TSharedPtr ObjPtr2 = ObjWeakPtr.Pin();
// 判断有效性
if (!ObjPtr2)
{
UE_LOG(LogTemp, Warning, TEXT("弱指针已空"));
}
// 重置
ObjWeakPtr.Reset();
ObjWeakPtr_Copy = nullptr;
输出:
step1 引用计数:ObjPtr[1]
step2 引用计数:ObjPtr[1]
step3 引用计数:ObjPtr[0]
弱指针已空
TWeakPtr 的 Pin() 函数可以将 WeakPtr 转成 TSharedPtr。
void MyWeakPtrToSharePtr()
{
TSharedPtr ptr = MakeShareable(new TestA());
//通过SharePtr初始化TWeakPtr
TWeakPtr WeakPtr = ptr;
//利用 WeakPtr的.Pin() 函数将 WeakPtr 转成SharedPtr
TSharedPtr WeakPtrToSharePtrObj = WeakPtr.Pin();
if (WeakPtrToSharePtrObj.IsValid())
{
//tdo smth...
}
}
TWeakPtr 无法直接转换为 TSharedRef,需要先将 WeakPtr 转成 SharedPtr,再将 SharedPtr 转 SharedRef。
void MyWeakPtrToShareRef()
{
TSharedPtr ptr = MakeShareable(new TestA());
//通过SharePtr初始化TWeakPtr
TWeakPtr WeakPtr = ptr;
//利用WeakPtr的.Pin()函数将WeakPtr转成SharedPtr
TSharedPtr WeakPtrToSharePtrObj = WeakPtr.Pin();
if (WeakPtrToSharePtrObj.IsValid())
{
//利用TSharedPtr的ToSharedRef()函数将TSharedPtr转换成TSharedRef
TSharedRef ref = WeakPtrToSharePtrObj.ToSharedRef();
}
}
注意:TWeakPtr 一定是从 TSharedPtr 或者 TSharedRef 过来的。
TUniquePtr 指向的对象只能被唯一指向,因而 Unique 指针不能赋值给其它指针。
不要为共享指针或共享引用的对象创建唯一指针。
// 创建唯一指针
TUniquePtr ObjUniquePtr = MakeUnique();
UE_LOG(LogTemp, Warning, TEXT("Validity: ObjUniquePtr[%d]"), ObjUniquePtr.IsValid());
// 判断有效性
if (ObjUniquePtr.IsValid())
{
ObjUniquePtr->ExeFun(); // 解引用
}
// 释放指针,移交
TUniquePtr ObjUniquePtr2(ObjUniquePtr.Release());
UE_LOG(LogTemp, Warning, TEXT("Validity: ObjUniquePtr[%d], ObjUniquePtr2[%d]"), ObjUniquePtr.IsValid(), ObjUniquePtr2.IsValid());
// 重置
ObjUniquePtr.Reset();
ObjUniquePtr2 = nullptr;
先声明两个类
class Father
{
public:
int32 a = 10;
};
class Child :public Father
{
public:
int32 b = 20;
};
派生类转基类为隐式转换。
//声明基类共享指针_father
TSharedPtr_father;
//创建派生类共享指针_Child
TSharedPtr_child = MakeShareable(new Child);
//派生类转换为基类,支持隐式转换
_father = _child;
通过 StaticCastSharedRef 进行转换。
//基类转换为派生类
TSharedPtr_child = StaticCastSharedPtr(_father);
//_child调用派生类的成员
if (_child.IsValid())
{
_child->b;
}
常量基类转换为派生类,即将 Const TSharePtr<基类> 转换为 TSharePtr<派生类>,有两种方式:
方法一:
ConstCastSharedRef 转换城非 Const 的基类StaticCastSharedRef 转换//创建派生类转为基类的共享指针,支持隐式转换
const TSharedPtr const_father = MakeShareable(new Child);
//先将基类指针转换为指向基类的指针
TSharedPtr const_father_a = ConstCastSharedPtr(const_father);
//然后基类转换为派生类
TSharedPtr _child_a = StaticCastSharedPtr(const_father_a);
//调用派生类成员
if (_child_a.IsValid())
{
_child_a->b;
}
方法二:直接通过 StaticCastSharedRef 转换
TSharedPtr _child_a = StaticCastSharedRef(const_father);
if (_child_a.IsValid())
{
_child_a->b;
}
注意:不能通过 ConstCastSharedPtr 直接转成派生类.
//错误写法, 不能通过 ConstCastSharedPtr 直接转成派生类.
TSharedPtr ptrcast = ConstCastSharedPtr(const_father);
用法和共享指针一样,只需修改以下即可
继承了 TSharedFromThis 其实里面会多一个弱引用指针(WeakPtr),存了一个对象,同时继承了 AsShared 方法,来获取这个类的共享引用,方便我们直接做转换。
将一个类继承自
TSharedFromThis之后,那么这个类就会知道自己是属于哪一个共享指针了。
TSharedFromThis 哪些场合适合使用?
先声明一个类 ClassBase 继承自 TSharedFromThis:
class ClassBase : public TSharedFromThis
{
public:
int32 nBase;
};
class ClassTop : public ClassBase
{
public:
void MyFunc() {}
};
TSharedFromThis 的使用:
不要在构造函数中调用 AsShared 或 Shared,共享引用此时并未初始化,将导致崩溃或断言。
void MySharedFromThisTest()
{
TSharedPtr ptr = MakeShareable(new ClassTop());
if (ptr.IsValid())
{
//我们通过.Get()将TSharedPtr转换成C++ 原生指针
ClassTop* pOriginPtr = ptr.Get();
//现在我们想将pOriginPtr这个原生C++指针怎么转回智能指针呢?
{
//错误的用法
//TSharedPtr ptr = MakeShareable(pOriginPtr);
//错误的用法, AsShared()不能用派生类接
//TSharedPtr ptr22 = pOriginPtr->AsShared();
}
{
//正确的用法, AsShared()不能用基类接(基类指的是你继承自TSharedFromThis的那个类)
TSharedPtr ptr2 = pOriginPtr->AsShared();
TSharedPtr ptr3 = StaticCastSharedPtr(ptr2);
if (ptr3.IsValid())
{
ptr3->MyFunc();
}
}
}
}
注意
TSharedPtr ptr = MakeShareable(pOriginPtr);
TSharedPtr ptr22 = pOriginPtr->AsShared();
TSharedPtr ptr2 = pOriginPtr->AsShared();
TSharedPtr ptr3 = StaticCastSharedPtr(ptr2);
if (ptr3.IsValid())
{
ptr3->MyFunc();
}
SharedThis 示例:
// 基类
class BaseClass :public TSharedFromThis
{
public:
BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual ~BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual void ExeFun() {
TSharedRef 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, 因而编译不通过
// TSharedRef AsSharedRef = AsShared();
TSharedRef AsSharedRef = SharedThis(this);
}
};
TSharedPtr BaseClassPtr = MakeShared();
UE_LOG(LogTemp, Warning, TEXT("引用计数:BaseClassPtr[%d]"), BaseClassPtr.GetSharedReferenceCount());
BaseClass* tempPtr = BaseClassPtr.Get();
TSharedPtr BaseClassPtr_Shared = tempPtr->AsShared();
UE_LOG(LogTemp, Warning, TEXT("引用计数:BaseClassPtr[%d], BaseClassPtr_Shared[%d]"),
BaseClassPtr.GetSharedReferenceCount(),
BaseClassPtr_Shared.GetSharedReferenceCount());
// 使用下面语句运行,程序死机
// TSharedPtr BaseClassPtr_New = MakeShareable(tempPtr);
输出:
引用计数:BaseClassPtr[1]
引用计数:BaseClassPtr[2], BaseClassPtr_Shared[2]
const & 进行传递。