ON_SCOPE_EXIT

UE 中有一个宏ON_SCOPE_EXIT,它的用法为:

1
2
3
4
5
// Make sure module info is added to known modules and proper delegates are fired on exit.
ON_SCOPE_EXIT
{
FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
};

UE4 Modules:Find the DLL and load it 里曾提到过这种用法:

这段代码的意思是在退出当前的作用域 (Scope) 时执行 {} 中的逻辑,简单地来说,它定义了一个当前作用域的对象并托管了一个 Lambda,在离开当前作用域的时候通过 C++ 的 RAII 机制来调用托管的 Lambda.

今天来简单分析一下它的实现。

首先,ON_SCOPE_EXIT的宏定义为:

1
#define ON_SCOPE_EXIT const auto ANONYMOUS_VARIABLE(ScopeGuard_) = ::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&]()

由此展开,最开始的那个使用的宏就等价替换为:

1
2
3
const auto ANONYMOUS_VARIABLE(ScopeGuard_) = ::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&](){
FModuleManager::Get().AddModuleToModulesList(InModuleName, ModuleInfo);
};

通常 lambda 用在谓词和调用的时候还好理解,但是这个搞了个 + 有点不走寻常路,由此可以推断类型 ::ScopeExitSupport::FScopeGuardSyntaxSupport 必然重载了 operator+ 操作符,并且是个模板函数。

查看 ::ScopeExitSupport::FScopeGuardSyntaxSupport 的代码:

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
// Runtime\Core\Public\Misc\ScopeExit.h
namespace ScopeExitSupport
{
/**
* Not meant for direct consumption : use ON_SCOPE_EXIT instead.
*
* RAII class that calls a lambda when it is destroyed.
*/
template <typename FuncType>
class TScopeGuard : public FNoncopyable
{
public:
// Given a lambda, constructs an RAII scope guard.
explicit TScopeGuard(FuncType&& InFunc)
: Func(MoveTemp(InFunc)){}

// This constructor needs to be available for the code to compile.
// It will be almost definitely be RVOed out (even in DEBUG).
TScopeGuard(TScopeGuard&& Other)
: Func(MoveTemp(Other.Func))
{
Other.Func.Reset();
}

// Causes
~TScopeGuard()
{
if (Func.IsSet())
{
Func.GetValue()();
}
}

private:
// The lambda to be executed when this guard goes out of scope.
TOptional<FuncType> Func;
};

struct FScopeGuardSyntaxSupport
{
template <typename FuncType>
TScopeGuard<FuncType> operator+(FuncType&& InFunc)
{
return TScopeGuard<FuncType>(Forward<FuncType>(InFunc));
}
};
}

果然!类型 FScopeGuardSyntaxSupport 通过模板重载了 operator+,它要求接收一个重载了operator() 的泛型 ** 函数对象 (functional object)** 类型,据此产生一个TScopeGuard 的类型,并将传递进来的函数对象托管,在这个 TScopeGuard 的对象的析构函数中 (destructor) 调用了托管的函数对象。

其中隐藏的关键是通过 ::ScopeExitSupport::FScopeGuardSyntaxSupport() + [&](){} 得到的 TScopeGuard 的对象是个 ** 局部对象 (automattic storage duration)**,只存在于当前的作用域(Scope) 中,在离开当前作用域时会自动销毁(调用析构函数),离开作用域时局部变量的销毁顺序为按定义的逆序执行。

[ISO/IEC 14882:2014 § 6.6]On exit from a scope (however accomplished), objects with automatic storage duration (3.7.3) that have been constructed in that scope are destroyed in the reverse order of their construction.