C++では「宣言(declaration)」と「定義(definition)」という2つの概念があり、これらを正しく理解していないと、リンクエラーや設計の破綻につながります。
一見すると似た言葉ですが、役割は明確に異なります。
宣言とは何か
宣言とは、ある名前が存在することをコンパイラに知らせる行為です。
この段階では「どこにあるのか」「どのような実体なのか」までは示さず、あくまで「この名前を後で使うので覚えておいてください」という通知に過ぎません。
宣言の目的は、コンパイル時の型チェックや名前解決を可能にすることです。
つまり、宣言があることでコンパイラは「この名前は正しい」「型は合っている」と判断できるようになります。
重要な点として、宣言だけでは実体は作られません。
メモリが確保されたり、処理内容が決まったりすることはありません。
定義とは何か
定義とは、その名前に対応する実体を与えることです。
変数であればメモリが確保され、関数であれば実際の処理内容が確定します。
定義は「これが本体です」と示す行為であり、プログラムとして動作するために必ず必要になります。
宣言だけではプログラムは完成しません。
宣言と定義の関係性
C++では、宣言は何度書いても問題ありませんが、定義には厳しい制約があります。
原則として、1つのプログラムの中で、同じ実体に対する定義は1つでなければなりません。
この制約は「One Definition Rule(ODR)」と呼ばれ、C++の設計の根幹を成すルールです。
このルールに違反すると、コンパイルは通ってもリンク時にエラーが発生します。
ただし、「定義は必ず1回だけ」という説明は厳密には不完全です。
C++には例外があり、特定の条件を満たす場合には、複数の翻訳単位に同一の定義を置くことが許されています。
この点は後述します。
変数における宣言と定義の注意点
よくある誤解として、「初期化されていない変数は宣言である」というものがありますが、これは正しくありません。
名前空間スコープ(いわゆるグローバル領域)で書かれた変数は、初期化がなくても定義です。
その場合、値は自動的にゼロ初期化されます。
また、「extern」が付いていれば宣言になると思われがちですが、externが付いていても初期化があれば定義になります。
この挙動は初心者が特に混乱しやすいポイントです。
関数における宣言と定義
関数の場合、引数の型や戻り値だけを示して処理内容を書かないものが宣言です。
一方で、実際の処理を含むものが定義です。
関数も変数と同様に、宣言は複数回可能で、定義は原則1回のみです。
そのため、一般的にはヘッダファイルに宣言を書き、ソースファイルに定義を書く構成が採用されます。
クラスにおける宣言と定義
クラスも同様に、名前だけを知らせる宣言と、メンバ構成まで含めた定義があります。
名前だけを知らせる宣言は「前方宣言」と呼ばれ、依存関係を減らすために頻繁に使われます。
前方宣言されたクラスは「完全型ではない」ため、サイズが必要な操作には使えませんが、ポインタや参照として扱うことはできます。
クラスの定義が行われた時点で、メンバ変数やメンバ関数が確定し、サイズが決まります。
なぜ宣言と定義を分けるのか
宣言と定義を分ける最大の理由は、分割コンパイルを可能にするためです。
C++では、各ソースファイルが独立した翻訳単位としてコンパイルされ、最後にリンクされます。
ヘッダファイルはその翻訳単位を作るための「設計図」として機能します。
この仕組みにより、
- 修正の影響範囲を限定できる
- ビルド時間を短縮できる
- 依存関係を整理しやすくなる
といった利点が生まれます。
「定義は1回だけ」の重要な例外
ここは非常に重要なポイントです。
C++では以下のようなものについては、同一内容であれば複数の翻訳単位に定義が存在しても問題ありません。
- inline関数
- テンプレート
- C++17以降のinline変数
- 条件を満たしたクラス定義
これらはODR上の特別な扱いを受けるため、ヘッダファイルに定義を書くことが一般的です。
この例外を知らずに「定義は必ず1回だけ」と覚えてしまうと、現代的なC++コードが理解できなくなります。
ヘッダファイルに定義を書くことの危険性
原則として、通常の変数や通常の関数の定義をヘッダファイルに書くと、多重定義によるリンクエラーを引き起こします。
これはヘッダが複数の翻訳単位に展開されるためです。
ただし、前述した例外(inline、テンプレート、inline変数など)に該当する場合は、この限りではありません。
重要なのは「ヘッダに書いていいかどうか」ではなく、ODR上許されているかどうかです。
実務での理解のまとめ
C++における宣言と定義は、次のように捉えると整理しやすくなります。
- 宣言は「インターフェース」
- 定義は「実装」
- ヘッダは最小限の宣言を置く場所
- ソースは実体を置く場所
この考え方を軸にすれば、リンクエラーに悩まされることは大幅に減ります。
最後に
宣言と定義の違いは、単なる文法知識ではなく、C++のビルドモデルそのものに直結しています。
ここを正確に理解できると、ヘッダ設計、依存関係管理、ビルド高速化といった一段上の設計に自然と進めるようになります。
この理解は、C++を「書ける」から「設計できる」言語へ引き上げるための重要な土台です。
以上、C++の宣言と定義の違いについてでした。
最後までお読みいただき、ありがとうございました。
