Unreal Engine C++ で開発するために 01

はじめに

Unreal Engineで開発するにあたってC++の使い方についてある程度の知見が溜まったのでノートをとります. また, C++は"シープラスプラス"と発音します, それ以外の発音方法はありません.

標準ライブラリ

対象のプラットフォームによってコンパイラ(付属の標準ライブラリ)が異なるため, 深く理解している自信がないならば, Unreal Engineの用意したライブラリを使うべきです.
特に::mallocに関しては, FMemory::Mallocと管理情報が別になってしまう可能性があるため, FMemory::Mallocに統一します.

Unreal Engine以外でも使うためにUnreal Engineのライブラリを一切使わないという選択をしたいことがあるかもしれませんが,
各コンパイラ, ライブラリ, プラットフォームについて深い知識が必要です.

print系

文字コードの問題もあるため, FStringで置き換えます. 製品コードで使うなら速度を考える必要があるかもしれませんが, 開発・デバッグ用のコードにprint系を使う理由はないはずです.

TEXTの存在が面倒ですが, printfを使うよりはずっと安全で楽でしょう.
C#のようにフォーマットを細かく指定はできませんが, 公式ドキュメントに説明がなくエンジン内でもフォーマットしていのようなものは使われていないため, 今後もそうならないと思われます.

FString contenxt = TEXT("内容");
FString str = FString::Format(TEXT("リスト {0} - {1}"), {*content, 1});

その他

POSIXのライブラリ関数群とSTLに関してはよく使うものは対応する機能が用意されているはずだと思って探すとよいでしょう.

  • メモリ確保, メモリ操作
  • 基本的数学, 幾何学
  • ファイルシステム:FPath, FFileHelper
  • 時刻:FTime
  • 文字列操作
  • 文字コード
  • データ構造
  • 暗号
  • ハッシュ
  • GUID
  • アルゴリズム, ユーティリティ

ソースコードの文字コード

ここでは問題は起きないと思われるため, 文字集合と符号化方式を混同して文字コードとします.

結論から"BOM付きUTF-8"一択です. この問題はコンパイラが関係しています.
旧くはMSVCとその他のコンパイラでソースコードの文字コードを統一することが難しかったのですが, 今はコンパイラの改善でそれほど難しくありません.
しかし, Unreal Engineが挟まることでこの問題が再燃します.

Unreal Engineにおける各コンパイラについて簡単にまとめますと,

  • Clang
    • Windows系以外で使用されます.
  • MSVC
    • Windows系に使用されます, XBoxもMSVCです.
    • Clangに変更することはできますが, エンジン自身に警告が残っているため実質使用できません.
      • ちなみに, “C:\Program Files"にClangがインストールされていないと認識しません.

それぞれの文字コードの扱いと, Unreal Engineの設定は次のようになっています.

  • Clang
    • ソースコードの文字コードは-finput-charsetで指定することができます, デフォルトはUTF-8です. BOMにも対応しています.
    • -finput-charsetは指定されていないためデフォルトの設定です.
  • MSVC
    • BOMがない場合, cp932として処理します. BOMがある場合はUTF-8として処理します.
    • コンパイルオプション/source-charset:utf-8を指定すると, BOMがない場合はUTF-8として処理します.
      • 逆にcp932のソースコードがあると問題が発生することがあるということです.
    • UnrealはWindowsに関しては, /source-charset:utf-8が指定されています.

以上から, “BOM付きUTF-8"が最も問題が起きにくいと思われます. しかし, まだ終わりません, “BOM付きUTF-8"を強制する手段が少ないのです. 例えば, テンプレート(Engine/Content/Editor/Templates)をBOM付きにしても, Unreal EngineのC++ファイル生成は"BOMを無視してロード, 特定のキーワードを置換, 保存"ですのでBOMが消えてしまいます.
さらに最も簡単で問題が確実に起きない解決方法は, コメントも含めて"ASCII(Latin-1)以外は使わない"だと思います.

Unityビルド

Unityビルドは, C/C++のコンパイル単位を無視して, コンパイル単位(.c/.cpp)をひとつのファイルにまとめてコンパイルする方法です.

利点は,

  • インクルードが少なくなりビルド速度が速くなる可能性がある
  • リンク時最適化に似た, コンパイル単位を超えた最適化が期待できる

欠点は,

  • C/C++の仕様であるコンパイル単位を無視することによる問題
    • “.c/.cpp"内のプリプロセッサ定義や無名namespaceがコンパイル単位を超えてしまう可能性がある
    • コンパイル単位を超える最適化が許されないことを利用したコードに問題がでる可能性がある
  • コンパイル時のメモリ使用量が増加

どれくらいの単位でまとめるかはシステムごとに設定がありますが, 普通に運用する限りは不定だと思われます. 例えば, CMakeにはグループを明示する方法があるようです.

他の"Unityビルド"に対応したシステムを常用していないので比較はできませんが, Unreal Engineのビルドシステムは再コンパイルすべきファイルを取りこぼす挙動が頻発します.
特定のモジュールだけリビルドする方法として, 次のパターンを削除する方法が考えられます.

[ProjectDirectory]/Intermediate/Build/[Platform]/UE4Editor/[Build Conciguration]/[Module Name]/*.txt

例えば次のような感じです.

プロジェクト名/Intermediate/Build/Win64/UE4Editor/Development/モジュール名/*.txt

この”.txt"ファイルは各コンパイル単位(.c/.cpp)が依存するファイルを保存しています.

防御

UnityビルドとUnrealHeaderToolが引き起こす無用なトラブルを避けるため, 次のようなことが推奨されます.
プロジェクトの人員に動作を理解させるより禁止してしまった方が早いです.

  • namespaceを使わない
    • プロジェクトとモジュールごとに一意の接頭辞をつける
  • 無名namespaceも使わない
  • c/cppファイル内であっても, 一意の接頭辞を付けたプリプロセッサを使用する

フォーマッタ

今日では手でフォーマットを揃えないで, clang-formatやリンタを使うことが普通と思われますが, これもUnreal EngineのDSLと相性が悪いです.
具体的には, Slateのコードが崩れます. clang-formatの処理から除外するには次のコメントで囲みます.

// clang-format off
// clang-format on

特定アルゴリズム用の固定の数値テーブルなどでも必要となるので, 覚えておくとよいでしょう.

Coverity

Unreal EngineのDSLやマクロが静的解析ツールに指摘されることがあります. Slateのコードが指摘されると思います.

Coverityの場合は, 指摘された行の直前に次のようなコメントを入れると握りつぶすことができます (参考).

// coverity[EVENT_TAG]
/* coverity[EVENT_TAG] */

小文字マクロ

UEの命名規則がパスカルケースのため気にしていないのか, Windows.hのmin, maxのように危ないマクロです.

  • check, checkSlow
  • checkf, checkfSlow
  • checkCode
  • checkNoEntry
  • checkNoReentry
  • checkNoRecursion
  • unimplemented
  • verify, verifySlow
  • verifyf, verifyfSlow
  • ensure
  • ensureMsgf
  • ensureAlways

C++11以上の機能

UE4の時点では, C++14以下で全プラットフォームに対応できると思います. Unreal Engineに限らずですが既存のライブラリ・フレームワークを使う場合は注意する点がいくつかあります.

  • リスト初期化

    • 引数無しのコンストラクタがあった場合, {} で初期化するとゼロ初期化を意図していてもコンストラクタが呼ばれます
    • UE4の場合, FVector X {};は未初期化になるため, FVector X{EForceInit::ForceInitToZero;, FVector X = FVector::ZeroVector;とします
    • そもそも, 他人の作成したライブラリにはコンストラクタの仕様が変わり得るので, よほど安定したAPIでないかぎりリスト初期化は使わない方が安全でしょう
  • auto

    • UEのガイドラインにあるとおり, イテレータなどの型を知る必要が確実にない場合以外使わない方がよいでしょう. 私も UEの考え方に賛同します
    • ガイドラインでは他人がコードを見るとき, 型はプログラムを説明する重要な要素で, 型は見えるようにすべきとあります
      • 正しく設計できていると, 型名と関数名がアルゴリズムを教えてくれます
      • IDEがポップアップで型を表示してくれるという反論に対しては, GitHub等のVCSやコードレビューツールでも型がわかるようにすべきということが書かれています

フォント

0Oなど別のコードポイントの字体がほぼ同じに見えるフォントがあります. 一般的にも, ポ〇モンセンターのパンフレットなど問題になることもあります.
プログラマについては, タイプミスで気づかなかった場合のコストはインクリメンタルビルドなどでほぼ気にならなくなってきています.

UEでは事情がことなり, Unityビルドのために再コンパイル, 再ビルドのコストが非常に高いため, ビルド前に気づくことが重要になってしまいます.
次のフォントのようにタイプ時に気づけるようにしておきます.

  • Ricty Diminished
  • Myrica M
  • 更紗

まとめ