# 断言

此文为Assertions的原创翻译,本文内容版权归原文所有,仅供学习,如需转载望注本文地址,翻译不易,谢谢理解。

Assertions是用来验证一段代码其所基于前提的工具。这可能同验证一个指针是否为NULL一样简单,也可能同验证一个特定函数是否能用一样复杂,UE4提供了一系列宏来执行这些验证,作为宏它们可以在一些特定配置中不编译,以便因为性能或最终构建版本不需要它们时就不编译它们。如果你想去看下这些宏,可以在这个地方找到它们:

/UE4/Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h

这些运行时断言宏分成三类:中断执行,调试模式下中断执行,报告错误但不中断执行。第一和第三种类型在DO_CHECK定义时被编译,第二种类型在DO_GUARD_SLOW有定义时被编译。如果DO_CHECK和DO_GUARD_SLOW被定义时设置为0,这些宏将被禁用而不会影响执行。

# 中断执行

现在来看下第一类宏,这些宏将在断言失败时终止执行,如果你是在一个调试器内运行,断言将会导致断点,这样你就可以检查导致失败的原因。

# check

check(expression);

这个宏先执行表达式,如果表达式为false,中断执行。这个表达式只有在该宏编译进源码时执行,也就是DO_CHECK=1的时候,下面是check()宏最简单的形式:

check(Mesh != nullptr);
check(bWasInitialized && "Did you forget to call Init()?");
1
2

# verify

verify(expression);

在DO_CHECK被启用了,这个宏跟check()一样,但是当DO_CHECK被禁用了,这个表达式仍旧被执行。你可以使用它来检查变量值是否符合你的假设:

verify((Mesh = GetRenderMesh()) != nullptr);
1

# checkf

checkf(expression, ...);

该宏允许打印一些额外的信息以便帮助调试,同check()能编译时行为一样。

checkf(WasDestroyed, TEXT( "Failed to destroy Actor %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
checkf( TCString<ANSICHAR>::Strlen( Key ) >= KEYLENGTH( AES_KEYBITS ), TEXT( "AES_KEY needs to be at least %d characters" ), KEYLENGTH( AES_KEYBITS ) );
1
2

# verifyf

verifyf(expression, ...);

就像verify()一样,同样它在执行时可以打印额外的调试信息,就像checkf()一样。

verifyf(Module_libeay32, TEXT("Failed to load DLL %s"), *DLLToLoad);
1

# checkCode

checkCode(expression);

这个宏比check()有点小复杂,它在do/while循环中执行一次表达式,这个宏被放置在do/while括号中,它虽然在引擎中不常使用,一旦你需要它就会发现它很有用。就像check()宏一样,在DO_CHECK被禁用时不被编译。因为这不要使用有副作用效果的表达式,因为在DO_CHECK被禁用时它会被移除。

checkCode( if( Object->HasAnyFlags( RF_PendingKill ) ) { UE_LOG(LogUObjectGlobals, Fatal, TEXT("Object %s is part of root set though has been marked RF_PendingKill!"), *Object->GetFullName() ); } );
1

# checkNoEntry

checkNoEntry();

这个宏不接收表达式用来标记永不会执行的代码。

switch (MyEnum)
{
    case MyEnumValue:
        break;
    default:
        checkNoEntry();
        break;
}
1
2
3
4
5
6
7
8

# checkNoReentry

checkNoReentry();

该宏被用来阻止一个给定函数重复被调用,为那些只应该被调用一次的函数使用它。

void NoReentry()
{
    checkNoReentry();
}
1
2
3
4

# checkNoRecursion

checkNoRecursion();

同checkNoReentry执行一样的检查,它的名字更明白地表明了它的意图。

int32 Recurse(int32 A, int32 B)
{
    checkNoRecursion();
    return Recurse(A - 1, B - 1);
}
1
2
3
4
5

# unimplemented

unimplemented();

第一类中的最后一个宏,只在DO_CHECK启用时可用,它用来标记一个函数可以被覆写,或者不应该被某些类被调用因为这个函数还没有被实现。

class FNoImpl
{
    virtual void DoStuff()
    {
        // You must override this
        unimplemented();
    }
};
1
2
3
4
5
6
7
8

# 调试时中断执行

第二类断言宏只在DO_GUARD_SLOW被启用时执行。DO_GUARD_SLOW只为调试模式的构建启用,你可以为你的项目改变这点。这些断言可能会慢点,在开发或者发布版本时可能不需要这些过于繁琐的检查。这些宏执行的和它们对应较快版本所执行的是一样的。这些宏是checkSlow(),checkfSlow(),和verifySlow()。

checkSlow(List->HasCycle());
checkfSlow(Class->IsA(AActor::StaticClass()), TEXT("Class (%s) is wrong type"), Class->GetName());
verifySlow(LastValue == Current);
1
2
3

# 报告错误但不中断执行

最后一类运行时断言并不中断执行,相反它们被用来创建调用栈报告来帮助追踪问题。这些宏里的表达式常被放在条件判断中,它们只有在DO_CHECK激活时才能被使用。

# ensure

ensure(expression);

验证表达式,查看是否生成到当前点的调用堆栈。

if (ensure( InObject != NULL ))
{
InObject->Modify();
}
1
2
3
4

# ensureMsg

ensureMsg(expression, message);

验证表达式,然后使用额外的信息生成调用栈来作为报告的一部分。

ensureMsg(Node != nullptr, TEXT("Node is invalid"));
1

# ensureMsgf

ensureMsgf(expression, message, ...);

验证表达式,使用包含更多信息的调用栈来生成报告。就像checkf()和verifyf(),你可以包含上下文信息来帮助追踪问题。

if (ensureMsgf(!bModal, TEXT("Could not create dialog because modal is set to (%d)"), int32(bModal)))
{
    ...
}
1
2
3
4