# 智能指针

# 参考文档

# 应用场景

下面这段出自:虚幻4:智能指针基础 (opens new window)

UObject本身就可以帮我们做回收,做释放的。我们可以看到UE4中所有的类都是继承自UObject,这样的话当我们引擎被关闭的时候,它会帮我们自动释放这些资源,或者是我们手动去释放掉它,这样能保证能彻底删掉它。那么如果是原生的类呢?不是继承UObject的类,怎么办?

我们会遇到这样的问题:

  • 我们创建这个资源,然后忘记把它删除,于是它占的内存可能永远都无法被释放掉。
  • 或者我们创建这个资源,然后有其它的指针指向了它,结果你不小心把它释放了,其它指针依然保留了原始的信息,导致我们在访问这个类的时候而产生野指针奔溃。

为了防止这些问题的出现,智能指针就出现了,它就是用来管理这些非继承UObject的类。

# 总览

UE4的智能指针库是C++11标准实现,用来减轻内存的分配和追踪。它主要包括Shared Pointers, Weak Pointers和Unique Pointers。它也添加了Shared References,可以把它看做为非空Shared Pointers。这些类不能被UObject系统使用,因为Unreal Objects使用一个专为游戏代码优化内存追踪系统。

# 智能指针类型

智能指针的类型有:

  • TSharedPtr:共享指针拥有其引用的对象,防止该对象被删除,并在无共享指针或共享引用(见下文)引用其时,删除该对象。共享指针可为空,意味其不引用任何对象。非空共享指针可生成共享引用。
  • TSharedRef:与共享指针类似,只能引用非空对象。
  • TWeakPtrSharedPtr:与共享指针类似,但不拥有引用的对象,不影响其生命周期,不会阻止引用的对象被销毁。
  • TUniquePtr:唯一指针仅会显式拥有其引用的对象。仅有一个唯一指针指向给定资源,因此唯一指针可转移所有权,但无法共享。

上面的智能指针默认只能在单线程中访问,如果你需要在多线程中访问,使用线程安全版本的智能指针:

  • TSharedPtr<T, ESPMode::ThreadSafe>
  • TSharedRef<T, ESPMode::ThreadSafe>
  • TWeakPtr<T, ESPMode::ThreadSafe>
  • TSharedFromThis<T, ESPMode::ThreadSafe>

线程安全版本的智能指针比默认的智能指针慢点,这是因为它要把引用计数原子化,但是它们的行为却同常规C++指针保持统一了:

  • 读和复制总是线程安全的。
  • 写和重置需要被同步化后才能保持安全。

# 工具类工具函数

智能指针的相关工具类有:

  • TSharedFromThis:从该类派生的类会有AsShared或SharedThis函数,这些函数让你能获取所管理对象的引用(TSharedRef)。

相关工具函数有:

  • MakeShared和MakeShareable:从C++常规指针中创建共享指针,MakeShared会给一个新对象实例分配空间,引用计数器会在一个单独的内存块,但是这要求被管理对象需要有公共的构造函数,MakeShareable效率稍微会低些,但是即使被管理对象的构造函数是私有的也可使用,它能让你掌控一个你没有创建对象的控制权,支持在删除对象时自定义行为。
  • StaticCastSharedRef和StaticCastSharedPtr:静态映射工具函数,常用来向下转换为一个派生类型。
  • ConstCastSharedRef and ConstCastSharedPtr:把一个常量智能引用或指针转换为一个变量智能引用或指针。

# 注意事项

  • 尽量不用TSharedRef或TSharedPtr给函数传递数据,这会导致经常解除引用和引用计数加1,可使用传递引用对象,比如const &。
  • 你可以向前声明共享指针给不完整类型。
  • 共享指针并不兼容Unreal对象(UObject以及它的派生类),引擎对UObject管理有一个单独的内存管理系统,这两个系统并不兼容对方。
  • 共享指针在引擎较低层级是不太有用的,比如渲染。

# 智能指针的优劣

  • 阻止内存泄露:当没有共享引用时智能指针会自动删除对象。
  • 弱引用:弱指针中断了引用循环并阻止了悬空指针。
  • 可选线程安全:Unreal智能指针包含线程安全代码,可用于在多个线程中管理引用计数,当不需要多线程时刻不使用该特性来换取更好的性能。
  • 运行时安全:共享引用不能为空,总是可以被取消引用的。
  • 易于推断:比较容易地从观察者知道对象的拥有者。
  • 内存管理:在64为平台上,智能指针大小上只是C++指针的两倍,唯一不同的是唯一指针,同C++指针大小一样。

智能指针的优点是:

  • 所有操作是恒定时间完成。
  • 解除智能指针的引用同c++原始指针一样。
  • 复制智能指针不会复制分配内存。
  • 线程安全版的智能指针是不用加锁的。
  • c++的原生智能指针比如std::shared_ptr不是在所有平台上都可用。UE4的智能指针使得在所有平台和编译器上有更加一致的实现。
  • UE4智能指针能和UE的其他容器无缝协作,线程处理也更加方便。

智能指针的缺点:

  • 创建和复制智能指针比创建和复制C++原始指针开销要大。
  • 维护引用计数会增加基础操作的周期。
  • 给引用控制器分配了两个堆,使用MakeShared和MakeShareable阻止了二次分配,提升性能。

# 实操

  • 共享指针:共享指针可为空,意味其不引用任何对象。非空共享指针可生成共享引用。
  • 共享引用:注意共享引用声明的时候必须要初始化,且不能设置为NULL。
  • 弱指针:弱指针是对对象的弱引用,它的计数不会阻止对象被销毁,如果对象被其共享指针销毁,弱指针会自动被清空。弱指针没法直接访问对象,必须通过Pin()来获取共享指针访问对象。

下面来看一下实际使用:

# LearnSmartPointer头文件

LearnSmartPointer.h文件内容:

// Fill out your copyright notice in the Description page of Project Settings.
//==================================ALearnSmartPointer===========================
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LearnSmartPointer.generated.h"

class TargetClass
{
public:
    TargetClass()
    {
        UE_LOG(LogTemp,Warning,TEXT("Calls Target default constructor!"));
        name= "default";
        age=10;
    }
    
    TargetClass(FString nameT,int ageT)
    {
        UE_LOG(LogTemp,Warning,TEXT("Calls Target name,age constructor!"));
        name = nameT;
        age = ageT;
    };
    FString name;
    int age;
    FString ToString(){
        return "age:"+FString::FromInt(age)+";name:"+name;
    }
    TSharedPtr<TargetClass> selfReference;
};

class TargetSubClass :public TargetClass
{
public:

    /*TargetSubClass()
    {
        UE_LOG(LogTemp,Warning,TEXT("Calls TargetSubClass default constructor!"));	
    }*/
    TargetSubClass(FString nameT,int ageT):TargetClass(nameT,ageT)
    {
        UE_LOG(LogTemp,Warning,TEXT("call TargetSubClass name,int"));
    }
    void GetName()
    {
        UE_LOG(LogTemp,Warning,TEXT("temp str:%s"),*this->name);
    }
    FString ToString(){
        return TargetClass::ToString();
    }
};

class MySharedFromThis:public TSharedFromThis<MySharedFromThis>
{
public:
    FString name;
    //static TSharedRef<MySharedFromThis> GetInstance();
    static TSharedRef<MySharedFromThis> GetSharedInstance();

private:
    static TSharedPtr<MySharedFromThis> mySharedInstance;
};

UCLASS()
class TEST426PROJECT_API ALearnSmartPointer : public AActor
{
    GENERATED_BODY()
    
public:	
    // Sets default values for this actor's properties
    ALearnSmartPointer();
    void TestLearnSharedPointer()
    {
        //查看引用计数
        TSharedPtr<TargetClass> targetPtr;
        targetPtr = nullptr;
        targetPtr = MakeShareable(new TargetClass(TEXT("王五"),20));
        UE_LOG(LogTemp,Warning,TEXT("target reference count: %d"),targetPtr.GetSharedReferenceCount());
        TSharedPtr<TargetClass> targetPtr2;
        targetPtr2=targetPtr;
        UE_LOG(LogTemp,Warning,TEXT("target reference count: %d"),targetPtr.GetSharedReferenceCount());

        targetPtr2.Reset();
        UE_LOG(LogTemp,Warning,TEXT("target reference count after reset: %d"),targetPtr.GetSharedReferenceCount());

        //使用共享指针数组
        TArray<TSharedPtr<TargetClass>> arrayContainer;
        arrayContainer.Add(MakeShareable(new TargetClass(TEXT("张三"),22)));
        arrayContainer.Add(MakeShareable(new TargetClass(TEXT("老刘"),24)));
        for (auto ArrayContainer : arrayContainer)
        {
            UE_LOG(LogTemp,Warning,TEXT("target reference count: %s"),*ArrayContainer->ToString());
        }
        UE_LOG(LogTemp,Warning,TEXT("arrayContainer reference count: %d"),arrayContainer[0].GetSharedReferenceCount());

        //判断共享引用是否有效
        if(targetPtr.IsValid() || targetPtr.Get())
        {
            UE_LOG(LogTemp,Warning,TEXT("targetPtr引用有效"));
            //访问对象
            UE_LOG(LogTemp,Warning,TEXT("targetPtr name: %s"),*targetPtr->name); //不建议使用这种,建议使用Get获取对象引用。
            UE_LOG(LogTemp,Warning,TEXT("targetPtr age: %d"),targetPtr.Get()->age);
            //销毁对象
            //targetPtr.Reset();
        }
        
        //共享指针转换为共享引用,这个过程是不安全的
        TSharedRef<TargetClass> targetRefTemp(new TargetClass(TEXT("孙七"),29));
        targetRefTemp = targetPtr.ToSharedRef();
        UE_LOG(LogTemp,Warning,TEXT("targetPtr change to targetRef,name: %s"),*targetRefTemp->name);
        
        
    }

    void TestLearnSharedReference()
    {
        //共享指针可以为空,但是共享引用不能为空。
        TSharedRef<TargetClass> targetRef(new TargetClass(TEXT("李五"),27));
        //访问共享引用
        UE_LOG(LogTemp,Warning,TEXT("targetRef name: %s"),*targetRef->name);
        UE_LOG(LogTemp,Warning,TEXT("targetRef name: %s"),*(*targetRef).name);

        //共享引用转换为共享指针
        TSharedPtr<TargetClass> targetPtr = targetRef;
        UE_LOG(LogTemp,Warning,TEXT("targetPtr name: %s"),*targetPtr.Get()->name);

        //使用AsShared,得是智能指针。
        /*MySharedFromThis* mySharedFromThis = new MySharedFromThis();
        mySharedFromThis->name="hello shared from this";
        TSharedRef<MySharedFromThis> sharedFromThis = mySharedFromThis->AsShared();
        UE_LOG(LogTemp,Warning,TEXT("GetInstance()'s name: %s"),*MySharedFromThis::GetInstance()->name);
        */
        
        //由TSharedFromThis获取
        MySharedFromThis::GetSharedInstance()->name = "hello shared from this";
        UE_LOG(LogTemp,Warning,TEXT("GetSharedInstance()'s name: %s"),*MySharedFromThis::GetSharedInstance()->name);
        
    }

    void TestLearnWeakPointer()
    {
        TSharedPtr<TargetClass> targetPtr;
        targetPtr = MakeShareable(new TargetClass(TEXT("王五"),20));
        TWeakPtr<TargetClass> weakPtr= targetPtr;
        UE_LOG(LogTemp,Warning,TEXT("weak pointer get name:%s"),*weakPtr.Pin()->name);

        TSharedPtr<TargetClass> targetPtr2(weakPtr.Pin());
        
        targetPtr.Reset();
        if(!weakPtr.IsValid())
        {
            UE_LOG(LogTemp,Warning,TEXT("weak pointer is reset!"));
        }else
        {
            UE_LOG(LogTemp,Warning,TEXT("weak pointer is not reset!"));
        }
        
        UE_LOG(LogTemp,Warning,TEXT("changed to TSharedPtr:%s"),*targetPtr2.Get()->name);
        
    }

    void TestLearnCastPtr()
    {
        TSharedPtr<TargetClass> targetPtr = MakeShareable(new TargetClass(TEXT("王五"),20));
        TSharedPtr<TargetSubClass> targetSubClass = StaticCastSharedPtr<TargetSubClass>(targetPtr);
        UE_LOG(LogTemp,Warning,TEXT("target sub class:%s"),*targetSubClass->ToString());
        
        TSharedPtr<TargetClass> targetPtr2 = MakeShareable(new TargetSubClass(TEXT("王六"),22));
        TSharedPtr<TargetSubClass> targetSubClass2 = StaticCastSharedPtr<TargetSubClass>(targetPtr2);
        UE_LOG(LogTemp,Warning,TEXT("target sub class2:%s"),*targetSubClass2->ToString());
        
        const TSharedPtr<TargetClass> targetPtr3 = MakeShareable(new TargetSubClass(TEXT("王七"),23));
        TSharedPtr<TargetClass> targetPtr4 = ConstCastSharedPtr<TargetClass>(targetPtr3);
        TSharedPtr<TargetSubClass> targetSubClass3 = StaticCastSharedPtr<TargetSubClass>(targetPtr3);
        UE_LOG(LogTemp,Warning,TEXT("target sub class3:%s"),*targetSubClass3->ToString());
        
    }

    void TestLearnSharedFromThis()
    {
        TSharedPtr<MySharedFromThis> mySharedFromThis = MakeShareable(new MySharedFromThis());
        MySharedFromThis* sharedFromThis = mySharedFromThis.Get();
        if(sharedFromThis)
        {
            UE_LOG(LogTemp,Warning,TEXT("get sharedPtr from ptr"));
            sharedFromThis->AsShared();
        }

        /*MySharedFromThis* MySharedFromThisOrigin = new MySharedFromThis();
        if(MySharedFromThisOrigin)
        {
            UE_LOG(LogTemp,Warning,TEXT("get sharedPtr from origin ptr"));
            MySharedFromThisOrigin->AsShared();
        }*/
        
    }
    
    /*void TestMemoryLeak()
    {
        TSharedPtr<TargetClass> targetPtr;
        targetPtr = MakeShareable(new TargetClass(TEXT("王五"),20));
        targetPtr.Get()->selfReference = targetPtr;
        //delete targetPtr.Get();
        //targetPtr.Get()->selfReference.Reset();
        targetPtr.Reset();
        if(targetPtr.IsValid())
        {
            UE_LOG(LogTemp,Warning,TEXT("Test memory leak,target reference count: %d"),targetPtr.GetSharedReferenceCount());
        }else
        {
            UE_LOG(LogTemp,Warning,TEXT("target ptr is not valid!"));
        }
    }*/

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

# LearnSmartPointer源文件

LearnSmartPointer.cpp文件内容:

// Fill out your copyright notice in the Description page of Project Settings.
//===============================LearnSmartPointer.cpp======================================

#include "LearnSmartPointer.h"


TSharedRef<MySharedFromThis> MySharedFromThis::GetSharedInstance()
{
    if(!mySharedInstance.IsValid())
    {
        mySharedInstance = MakeShareable(new MySharedFromThis());
    }
    return mySharedInstance->AsShared();
}

TSharedPtr<MySharedFromThis> MySharedFromThis::mySharedInstance;

// Sets default values
ALearnSmartPointer::ALearnSmartPointer()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ALearnSmartPointer::BeginPlay()
{
    Super::BeginPlay();
    
    TestLearnSharedPointer();
    TestLearnSharedReference();
    TestLearnWeakPointer();
    //TestMemoryLeak();
    TestLearnCastPtr();
    TestLearnSharedFromThis();
    
}

// Called every frame
void ALearnSmartPointer::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45