One Definition Rule (ODR) とは、C++における基本的なルールであり、プログラム全体(すべての翻訳単位)で、各オブジェクト、関数、またはクラスについて、正確に1つの定義(実装)が必要であることを要求します。
ODRは次のような場合に違反されます:
これにより、捉えにくく予測不可能なバグ、リンクエラー、さらにはプログラムの挙動がビルド方法に依存することになります。
なぜ違反されるのか:
大規模なプロジェクトでは、しばしば.hファイルをバージョン管理なしでコピー/修正するか、独立したコンパイルのために多数のモジュールにコードを分離します。もし誰かがインライン関数を1箇所だけで変更すると、他のソースファイルは古いバージョンのままとなる可能性があります。
回避方法:
constexprまたは、C++17以降はinline変数を使用する。異なる.cppファイルで同じ名前と異なる実装を持つstatic関数(static void foo())を定義しても問題ありませんか?
多くの人は、static関数はモジュール間でお互いに影響を与えないと考えています。答えは、はい、できます。なぜなら、各関数は内部リンケージを持ち(その翻訳単位内でのみ可視)ます。ただし、インライン関数やテンプレートにはその保証はありません。これがしばしば混乱を招きます。
例:
// file1.cpp static void foo() { std::cout << "A"; } // file2.cpp static void foo() { std::cout << "B"; }
これらのモジュール内での呼び出しは独立しています。
逸話
大規模プロジェクトで1人の開発者が、.hファイルのクローンの1つのインライン関数の本体を変更しました。ビルド後、一部のモジュールは古いアルゴリズムで動作し、一部は新しいアルゴリズムで動作するという予測不可能な挙動が発生しました。原因は、インライン関数に対するODRの違反です。
逸話
C++17への移行時に、定数変数が複数のヘッダーファイルでinlineキーワードなしに定義されました。リンク時に多くの重複シンボルのエラーが発生しました。
inline const変数として宣言することで修正しました。
逸話
自動生成された.hファイルが同じテンプレートクラスのメソッドの2つの実装を、異なるビルドでのifdefチェックの違いで持っていることが遅れて判明しました。その後、アプリケーションは関連するモジュール間のABIの不一致により定期的にクラッシュしました。