UE4 智能指针和弱指针

一、序言

       我们知道C++里面有自己的指针,但是在虚幻引擎中原生C++的指针在分配和释放内存时会产生一些问题,比如无法被虚幻进行回收,造成内存泄漏等问题,于是虚幻就衍生出了智能指针。虚幻智能指针库是 C++11 智能指针的自定义实现,旨在减轻内存分配和跟踪的负担。此实现包括行业标准共享指针弱指针唯一指针。它还添加了类似于不可为空的共享指针的共享引用这些类不能与UObject系统一起使用,因为虚幻对象使用单独的内存跟踪系统,该系统针对游戏代码进行了更好的调整。

二、各个指针的含义

       共享指针(TSharedPrt):共享指针拥有它引用的对象,无限期地阻止删除该对象,并最终在没有共享指针或共享引用,引用它时处理它的删除。共享指针可以为空,这意味着它不引用任何对象。任何非空共享指针都可以生成对其引用的对象的共享引用。

       共享引用(TSharedRef):共享引用的作用类似于共享指针,因为它拥有它所引用的对象。它们在空对象方面有所不同;共享引用必须始终引用非空对象。因为共享指针没有这个限制,所以共享引用总是可以转换为共享指针,并且共享指针保证引用一个有效的对象。当您想要保证被引用的对象是非空的,或者如果您想要指示共享对象的所有权时,请使用共享引用。

       弱指针(TWeakPtr):弱指针类似于共享指针,但不拥有它们引用的对象,因此不会影响其生命周期。这个属性非常有用,因为它打破了引用循环,但这也意味着弱指针可以随时变为空,而不会发出警告。出于这个原因,弱指针可以生成指向它引用的对象的共享指针,从而确保程序员临时安全地访问该对象。

       唯一指针(TUniquePtr):唯一指针单独且明确地拥有它所引用的对象。由于给定资源只能有一个唯一指针,因此唯一指针可以转移所有权,但不能共享它。任何复制唯一指针的尝试都将导致编译错误。当唯一指针超出范围时,它会自动删除它引用的对象。
 

三、指针的用法

         为了更好的使用指针,虚幻提供了几个帮助类和函数,使使用智能指针更容易、更直观。

TSharedFromThis

TSharedFromThis添加AsSharedSharedThis函数派生您的类。这些函数使您能够获取TSharedRef到您的对象。
 

MakeSharedMakeShareable

从常规 C++ 指针创建共享指针。MakeShared在单个内存块中分配新的对象实例和引用控制器,但要求对象提供公共构造函数。MakeShareable效率较低,但即使对象的构造函数是私有的也可以工作,使您能够获得不是您创建的对象的所有权,并在删除对象时支持自定义行为。

StaticCastSharedRefStaticCastSharedPtr

静态转换实用函数,通常用于向下转换为派生类型。

ConstCastSharedRefConstCastSharedPtr

const智能参考或智能mutable指针分别转换为智能参考或智能指针。

四、使用智能指针的优缺点

优点:

  • std::Shared_Ptr不是在所有的平台上都可用。

  • 使得在所有的编译器和平台上更加一致性。

  • 可以和其他虚幻容器无缝协作。

  • 更好的控制平台特性,包括线程处理和优化。

  • 线程安全的功能。

  • 我们想在性能方面有更多的控制权(内敛函数,内存,虚函数等)。

  • 在不需要的时候不需要引入第三方依赖。

缺点:

  • 创建和复制智能指针比创建和复制原始 C++ 指针涉及更多开销。

  • 维护引用计数会增加基本操作的周期。

  • 一些智能指针比原始 C++ 指针使用更多内存。

  • 参考控制器有两个堆分配。使用MakeShared而不是MakeShareable避免第二次分配,并且可以提高性能。

五、案例测试

   

  • 原生C++与智能指针之间的转化

创建一个C++ Actor类 命名为 MyActor
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
class TaskA
{
public:
	int32 a;
	float b;
};//创建一个原生的C++类

UCLASS()
class HEXINU4_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	void TaskAA();//共享指针测试函数
       void TaskSharedRef();//共享引用测试函数
       void TaskSharedRefAndPtr();//共享指针和共享引用之间相互转化
       void TaskweakPrt();//弱指针的测试函数
        
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	TSharedPtr<TaskA> Task_a;//共享指针可以为 NULL 但是不可以复制,使用共享指针最好全局指针,不要创建临时指针好它比普通的指针耗资源
};
        // TSharedRef<TaskA> Task_b;//不能这么声明共享引用是错误的
           TWeakPrt<TaskA> Task_c;//弱指针的声明
void AMyActor::TaskAA()
{
	Task_a= MakeShareable(new TaskA());//共享指针创建一个智能指针
	if (Task_a.IsValid() || Task_a.Get())//判断共享指针是否有效或者解引用
	{
		Task_a.Get()->a;//可替换为Task_a->a;
		Task_a.Reset();
	}
}
void AMyActor::TaskSharedRef()
{
      TSharedRef<TaskA> Task_b(new TaskA());//共享引用必须初始化可用对象
       Task_b->a;
}
void  AMyActor::TaskSharedRefAndPtr()
{
      //共享引用转化成共享指针
       TSharedRef<TaskA> Task_b(new TaskA());
       Task_a = Task_b;
      //普通指针转化成共享指针
       TaskA *NewTaskA = new TaskA();
       Task_a = MakeShareable(new NewTaskA); 
      //注意
     // Task_b = nullptr;//错误的
        Task_b = Task_a.ToSharedRef();//共享指针转换成共享引用是不安全的它有个断言
}
 void AMyActor::TaskweakPrt()
{
      TSharedPtr<TaskA> _TaskA_ptr = MakeShareable(new TaskA());//共享指针
      TSharedRef<TaskA> _TaskA_ref(new TaskA());//共享引用
      TweakPtr<TaskA>Task_D(_TaskA_ptr);
      TweakPtr<TaskA>Task_K(_TaskA_ref);
      Task_c = Task_D;
      Task_c = Task_K;
        Task_c = nullptr;//防止对象被销毁
      //弱指针转化成智能指针
      TSharedPtr<TaskA> NewTask(Task_c.Pin());
       if(NewTask.IsValid())
         {
             NewTask->a;
         }

}
  • 智能指针之间的转化

class FBase
{
public:
protected:
private:
};

class FB : public FBase
{
public:
	void Printf() {};
protected:
private:
};
	
//将基类转化成派生类,访问派生类的函数和变量
	TSharedPtr<FBase> B_ = MakeShareable(new FB());//共享指针和UObject不兼容,UObject有自己的销毁机制
	TSharedPtr<FB> C_ = StaticCastSharedPtr<FB>(B_);
	if (C_.IsValid())
	{
		C_->Printf();
	}
	const TSharedPtr<FBase> E_ = MakeShareable(new FB());
	TSharedPtr<FBase> D_ = ConstCastSharedPtr<FBase>(E_);
	TSharedPtr<FB> F_ = StaticCastSharedPtr<FB>(D_);
	if (F_.IsValid())
	{
		F_->Printf();
	}
  • TSharedFromThis()的用法:保留一个对对对象的弱引用,方便直接转化成共享引用AsShared();

class TaskA:public TSharedFromThis<TaskA>
{
public:
	int32 a;
    float b;
};
.CPP文件中测试
       TSharedPtr<TaskA>NewTest = MakeShareable(new TaskA());
	newtask->a;
	TaskA *NewTest1 = NewTest.Get();//解引用将智能指针转化成原生指针
	if (NewTest1)
	{
		NewTest1->AsShared();
	}
	TaskA *currentTask = new TaskA();//原生指针
	TSharedRef<TaskA> Task_c = currentTask->AsShared();
  • 综合案例

#include "CoreMinimal.h"//

class IMyID//一个用户ID类
{
public:
	IMyID()
	{
		ID = FMath::RandRange(100, 1000);
	}
	FORCEINLINE uint64 GetID() { return ID; }//强制内敛ID
private:
	int32 ID;
};

class FData:public IMyID//数据继承ID类,有自己的数据参数
{  
public:
	FData()
	{
		Health = 100.f;
		bDeath = 0;
		PlayerName = TEXT("CharacterOne");
	}

	float Health;
	uint8 bDeath;
	FName PlayerName;
};

class FDataManager//数据管理类
{
public:
	static TSharedRef<FDataManager> Get()
	{
		if (DataMager.IsValid())
		{
			DataMager = MakeShareable(new FDataManager());
		}
		return DataMager.ToSharedRef();
	}                           
	TSharedRef<FData> CreatData()
	{
		TSharedPtr<FData> tmp_Data = MakeShareable(new FData());
		MyData.Add(tmp_Data->GetID(), tmp_Data);
		return tmp_Data.ToSharedRef();
	}
	~FDataManager()
	{
	}
private:
	static TSharedPtr<FDataManager> DataMager;
	TMap<uint64, TSharedPtr<FData>> MyData;
};
//角色实例显示
class FCharacter
{
public:
	FORCEINLINE bool IsValid()
	{ 
		return NewData.IsValid();
	}
	void SetNewData(TSharedRef<FData> CurrentNewData) { NewData = CurrentNewData; }
	void SetNewName( FString NewName)
	{
		if (NewData.IsValid())
		{
			NewData.Pin()->PlayerName = FName(*NewName);
		}
	}
	FORCEINLINE bool IsValid() { return NewData.IsValid(); } 
	
protected:
private:         
	TWeakPtr<FData> NewData;//弱指针不参与类的生命周期

};
void NewMain()//假设是一个场景
{
	FCharacter* Character = new FCharacter();

	Character->SetNewData(FDataManager::Get()->CreatData());
	Character->SetNewName("PlayerTwo");
}
//注意如果是一个裸指针(C++原生指针)当删除这个指针时会崩溃,产生野指针,解决方法是加上一个TSharedFromThis(FData),让它始终被一个弱指针包裹
class FData:public IMyID,public TSharedFromThis(FData)//数据继承ID类,有自己的数据参数
{  
public:
	FData()
	{
		Health = 100.f;
		bDeath = 0;
		PlayerName = TEXT("CharacterOne");
	}

	float Health;
	uint8 bDeath;
	FName PlayerName;
};
	FData* CreatData()
	{
		TSharedPtr<FData> tmp_Data = MakeShareable(new FData());
		MyData.Add(tmp_Data->GetID(), tmp_Data);
		return tmp_Data.Get();
	}

void NewMain()//假设是一个场景
{
	FCharacter* Character = new FCharacter();
          FData* New_Data = FDataManager::Get()->CreatData();
	Character->SetNewData(New_Data->AsShared());
	Character->SetNewName("PlayerTwo");
}

要拷贝结构体指针,你可以通过以下步骤来完成: 1. 首先,创建一个新的结构体对象,该对象将用于存储拷贝的数据。 2. 使用适当的内存分配函数(如`malloc`)为新结构体对象分配内存空间。 3. 使用`memcpy`函数将原始结构体指针指向的数据拷贝到新的结构体指针指向的内存空间中。 下面是一个示例代码片段,演示了如何拷贝结构体指针: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { int id; char name[20]; } Student; void copyStructPointer(Student* src, Student** dest) { // 为目标指针分配内存空间 *dest = (Student*)malloc(sizeof(Student)); // 拷贝数据 memcpy(*dest, src, sizeof(Student)); } int main() { Student* original = (Student*)malloc(sizeof(Student)); original->id = 1; strcpy(original->name, "John"); Student* copied; copyStructPointer(original, &copied); printf("Original: id = %d, name = %s\n", original->id, original->name); printf("Copied: id = %d, name = %s\n", copied->id, copied->name); free(original); free(copied); return 0; } ``` 在上述示例中,我们定义了一个`Student`结构体,并在`main`函数中创建了一个原始的结构体指针`original`。然后,我们调用`copyStructPointer`函数来拷贝`original`指向的数据,并将拷贝结果存储在`copied`指针中。最后,我们打印出原始和拷贝的结构体的数据,并释放内存空间。 注意,拷贝结构体指针实际上是拷贝指针所指向的数据,而不是重新创建一个指向相同数据的新指针。因此,在使用拷贝后的结构体指针时要小心,确保拷贝指针仍然指向有效的内存空间。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞起的猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值