および C++ プログラミングでは、assert
が開発中の予期しない条件や無効なランタイム条件の検出と診断に役立ちます。このような条件では、多くの場合、ポインターが null でないこと、除数が 0 でないこと、関数が再帰的に実行されていないこと、またはコードが必要とするその他の重要な前提条件を検証しますが、毎回検証を実行するのは非効率的です。また、assert
は以降のティックで必要となるオブジェクトを削除してしまうといったような、実際のクラッシュを発生させる前の遅延クラッシュを引き起こすバグを検出し、デベロッパーが最終的なクラッシュの根本原因を検出するのに役立つこともあります。assert
の主な特徴は、シッピング コードには存在しないということです。つまり、リリースされた製品のパフォーマンスに影響を与えることはなく、副作用も一切ありません。assert
の仕組みを簡単に説明すると、「アサートされた」ものはすべて true である必要があり、true でなければプログラムの実行が停止します。
Unreal Engine では、同等の 3 種類の assert
ファミリ、check
、verify
、ensure
を使用できます。これらの機能を基盤として使用しているコードを確認する場合は、「Engine/Source/Runtime/Core/Public/Misc/AssertionMacros.h
」に該当するマクロがあります。各マクロの動作はわずかに異なるものの、どのマクロも開発時に使用する診断ツールと同じ一般的な役割を果たします。
Check
Check ファミリは基本の assert
に最も近く、このファミリのメンバーは最初のパラメータの値が false と評価されると実行を停止します。また、シッピング ビルドではデフォルトで実行されません。使用可能な Check マクロは次のとおりです。
マクロ | パラメータ | 動作 |
---|---|---|
check または checkSlow |
Expression |
Expression が false の場合、実行を停止します |
checkf または checkfSlow |
Expression , FormattedText , ... |
Expression が false の場合、実行を停止し、ログに FormattedText を出力します |
checkCode |
Code |
1 回実行される do-while ループ構造内で Code を実行します。主に、別の Check に必要な情報を準備するのに役立ちます。 |
checkNoEntry |
(なし) | check(false) と同様に、この行がヒットした場合実行を停止します。ただし、これは到達不能なコード パスを対象としています |
checkNoReentry |
(なし) | この行が 2 回以上ヒットした場合、実行を停止します |
checkNoRecursion |
(なし) | スコープを離れることなくこの行が 2 回以上ヒットした場合、実行を停止します |
unimplemented |
(なし) | check(false) と同様に、この行がヒットした場合は実行を停止します、ただし、これはオーバーライドされるべき仮想関数および呼び出されるべきではない仮想関数を対象としています |
Check マクロは、デバッグ、開発、テスト、およびシッピング エディタ ビルドで動作します。ただし、末尾が「Slow」であるマクロは除きます。このマクロはデバッグ ビルドでのみ動作します。true の値 (通常 1
) を保持するように USE_CHECKS_IN_SHIPPING
を定義すると、Check マクロはすべてのビルドで動作します。これは、Check マクロ内のコードが値を変更していたり、追跡困難なシッピングのみのバグがあるという疑いがあり、既存の Check マクロでキャッチされるという疑いがある場合に役立ちます。プロジェクトのシッピングでは、USE_CHECKS_IN_SHIPPING
をデフォルト値である 0
に設定する必要があります。
Verify
Verify ファミリは、ほとんどのビルドで Check ファミリと同一の動作を行います。ただし、Check マクロが無効になっているビルドでも、Verify マクロは式を評価します。診断チェックとは別に式を実行する必要がある場合にのみ、Verify マクロを使用する必要があります。例えば、アクションを実行し、そのアクションの成否を示す bool
値を返す関数がある場合は、Check ではなく Verify を使用して、アクションが成功したことを確認する必要があります。これは、シッピング ビルドでは Verify は戻り値を無視して、引き続きアクションを実行するためです。ただし、Check はシッピング ビルドで関数をまったく呼び出さないため、動作が異なります。
マクロ | パラメータ | 動作 |
---|---|---|
verify または verifySlow |
Expression |
Expression が false の場合、実行を停止します |
verifyf または verifyfSlow |
Expression , FormattedText , ... |
Expression が false の場合、実行を停止し、ログに FormattedText を出力します |
Verify マクロは、デバッグ、開発、テスト、およびシッピング エディタ ビルドで完全に動作します。ただし、末尾が「Slow」であるマクロは除きます。これは、デバッグ ビルドでのみ動作します。true の値 (通常 1
) を保持するために USE_CHECKS_IN_SHIPPING
を定義すると、この動作はオーバーライドされます。他のどの場合でも、Verify マクロは式の評価は行いますが、実行の停止や、テキストのログ出力は行いません。
Ensure
Ensure ファミリは Verify ファミリに似ていますが、致命的ではないエラーで動作します。つまり、Ensure マクロの式が false と評価された場合、エンジンはクラッシュ レポート機能に通知するものの、実行は継続します。クラッシュ レポート機能のフラッディングを回避するために、Ensure マクロでは、エンジンまたはエディタ セッションごとに 1 度のみレポートします。ユース ケースで、式が false と評価されるたびに、Ensure マクロで報告する必要がある場合は、「Always」バージョンのマクロを使用します。
マクロ | パラメータ | 動作 |
---|---|---|
ensure |
Expression |
Expression が初めて false になったときにクラッシュ レポート機能に通知します |
ensureMsgf |
Expression , FormattedText , ... |
Expression が初めて false になったときに、クラッシュ レポート機能に通知し、FormattedText をログに出力します |
ensureAlways |
Expression |
Expression が false の場合、クラッシュ レポートに通知します |
ensureAlwaysMsgf |
Expression , FormattedText , ... |
Expression が false の場合、クラッシュ レポート機能に通知し、FormattedText をログに出力します |
Ensure マクロはすべてのビルドで式を評価しますが、デバッグ、開発、テスト、およびシッピング エディタのビルドでのみクラッシュ レポート機能に通知します。
使用例:
次の仮定の状況で、Check、Verify、および Ensure がコードの明確化やデバッグの支援に役立つユース ケースを示しています。
// この関数は、NULL JumpTarget で決して呼び出されないようにしてください。そのような場合は、プログラムを停止してください。
void AMyActor::CalculateJumpVelocity(AActor* JumpTarget, FVector& JumpVelocity)
{
check(JumpTarget != nullptr);
// (JumpTarget に着地するために必要な速度を計算します。これで、JumpTarget が null でないことが確信できました)。
}
// これは Meshの値を設定し、それが非 NULL であることを期待する。その後、Mesh が NULL であった場合はプログラムを停止します。
// この式には副作用 (Mesh の設定) があるため、check の代わりに verify を使用しています。
verify((Mesh = GetRenderMesh()) != nullptr);
// このコードの行は、製品のシッピング版で発生する可能性のある小さなエラーを捕捉します。
// このエラーは、実行を停止せずに処理できる程度の軽微なものです。
// バグを修正した後でも、そのバグが発生するかどうかを知りたい場合もあります。
void AMyActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
// bWasInitialized が true であることを確認してから処理を進める。false の場合は、バグがまだ修正されていないことをログに残します。
if (ensureMsgf(bWasInitialized, TEXT("%s ran Tick() with bWasInitialized == false"), *GetActorLabel()))
{
// (正しく初期化されたAMyActorを必要とする処理を行う。)
}
}
// このコードは、新しいシェイプタイプを追加した場合に停止しますが、このスイッチブロックでそれを処理することを忘れています。
switch (MyShape)
{
case EShapes::S_Circle:
// (Handle circles.)
break;
case EShapes::S_Square:
// (Handle squares.)
break;
default:
// すべての形状タイプに対応するケースがあるはずなので、このようなことは決して起こらないはずです。
checkNoEntry();
break;
}
// このUObjectには、IsEverythingOKというテスト関数があり、副作用はないが、問題があればfalseを返す。
// この場合、致命的なエラーで終了させる。
// このコードには副作用がなく、診断の目的しかないので、出荷時のビルドで実行する必要はない。
checkCode(
if (!IsEverythingOK())
{
UE_LOG(LogUObjectGlobals, Fatal, TEXT("Something is wrong with %s! Terminating."), *GetFullName());
}
);
// このリストにサイクルがあってはならず、サイクルがあるとプログラムはハングします。しかし、サイクルのチェックは時間がかかるので、デバッグビルドのときだけ行うことが望ましいです。
checkfSlow(!MyLinkedList.HasCycle(), TEXT("Found a cycle in the list!"));
// (リストを走査し、各要素でコードを実行します。)