ヘッダに関数の実装を書く場合はinline
を付けて多重定義を許容する必要あり
コンパイラが呼び出しコストを抑える目的で関数をインライン展開してくれることを期待し、短い関数の実装はあえてソースファイル側(.cpp)ではなくヘッダファイル側(.h/.hpp)に記述することがあります。
ただし、普通に書いてしまうと多重定義のリンカエラーが出てしまいます。
例えば、以下のようなヘッダファイルを書いて複数のソースファイルでインクルードするとエラーになります。
GCCであれば「multiple definition of "~"
」というエラーを吐いてしまうことでしょう。
// ※誤り: ヘッダファイルにこう書いた場合、複数のソースファイルでインクルードすると多重定義になってしまう
int add(int a, int b)
{
return a + b;
}
ヘッダに関数の実装を書く場合には、関数に対してinline
指定子を付ける必要があります。
inline
指定子を付けることで、関数が多重定義されることが許容されます。
// ヘッダファイルに実装を書く場合はinlineを付ける
inline int add(int a, int b)
{
return a + b;
}
なお、inline
指定子を付けたからといって、必ずしもインライン展開されるとは限らないので注意してください。
C++17より前はinline
指定子はインライン展開する場合のヒントにできることになっていましたが、C++17で廃止されました。
そのため、もはや現在のC++においてはinline
は多重定義を許容するためのキーワードでしかありません。
追記(2020/9/2 21:44):
上記の、「C++17からはinline
指定子がインライン展開のヒントに使用されなくなった」という情報は誤りでした。訂正いたします。
付けなくて良い場面も多いので付け忘れる
inline
を付けないとエラーになるのは、あくまで通常の関数の場合の話です。
以下のように、付けなくて良い場面もたくさんあります。
付けなくて良い例: メンバ関数の実装をクラス宣言の中に書く場合
例えば、クラスのメンバ関数には必ずしもinline
を付けなくても良いです。
class Person
{
public:
void sayHello() const // OK: 自動的にinline扱いになる
{
std::cout << "Hello World!" << std::endl;
}
};
inline
が必要となる例: メンバ関数をクラス宣言の外側に書く場合
ただし、クラス宣言の外にメンバ関数の定義を書いた場合はinline
を付けないとエラーになります。
class Person
{
public:
void sayHello() const;
};
// この形式で実装をヘッダに書く場合、inlineを付けないと多重定義になる
inline void Person::sayHello() const
{
std::cout << "Hello World!" << std::endl;
}
付けなくて良い例: 関数テンプレート
また、関数テンプレートにもinline
を付ける必要がありません。
関数テンプレートは使用時にコードが生成(インスタンス化)されるので、そもそも多重定義を禁止するルールがないためです。
template <typename T>
T Add(T a, T b) // 関数テンプレートは多重定義が禁止されていないので、inlineは付けなくて良い
{
return a + b;
}
(※ただし、ヘッダ内での関数テンプレートの完全特殊化は関数テンプレートではなく関数実体となるのでinline
が必要です。)
付けなくて良い例: メンバ関数テンプレート
同じく、クラスのメンバ関数テンプレートについても、クラス宣言の外に書いた場合でもinline
を付ける必要がありません。
class Hoge
{
public:
template <typename T>
T add(T a, T b) const;
};
// この場合も同じく、inlineは付けなくて良い
template <typename T>
T Hoge::add(T a, T b) const
{
return a + b;
}
inline
が必要なのによく忘れる例: ユーティリティ系の名前空間を作る場合
このように、クラスを使用する場合はヘッダに実装を書く場合でもinline
を付けなくて良い場面が多いので、いざという時に付け忘れることがあります。
特に、クラスと同じノリでユーティリティ系の関数を名前空間に実装する時に忘れやすいです。
例えば、JavaやC#でいうstaticクラスのような感じでnamespaceを作成する際、MathUtils
クラスを実装するようなノリでMathUtils
名前空間に関数を実装していると、以下のようにinline
を付け忘れることがあります。
// ※誤り(ヘッダファイルではなくソースファイルならこれで正解)
namespace MathUtils
{
int Add(int a, int b) // NG: inlineを付け忘れている!
{
return a + b;
}
}
ヘッダファイル内の関数にstatic
を付けると一応エラーは出なくなるが誤り
ヘッダファイル内に実装した関数にstatic
を付けるのは誤りです。
// ※誤り
namespace MathUtils
{
static int Add(int a, int b) // NG: ヘッダファイル内でstaticを付けた関数を定義してはいけない!
{
return a + b;
}
}
ソースファイル内でこれをインクルードすると、関数にstatic
が付いているため内部リンケージとなります(=関数宣言がソースファイル内でのファイルスコープを持つ)。
これにより、関数がそのソースファイル内でしか参照できなくなるため、結果的にはリンク時に衝突しなくなり、エラーにならなくなります。
しかし、ヘッダをインクルードしたソースファイルの翻訳単位ごとに関数の実体が生成されてしまいます。
その結果、同じ関数が大量の実体を持ち、オブジェクトコードの肥大化に繋がってしまいます。inline
の代わりとしてstatic
を書くのは避けましょう。
※ソースファイル内で関数を内部リンケージにするためにstatic
を付けるのはもちろんOKです。static
というキーワードには色んな意味があってややこしいので、C++で内部リンケージを意図する場合にはstatic
の代わりに無名名前空間を使う場合も多いことでしょう。
追記(2020/9/2):
コメントで指摘がありましたが、あえてヘッダ内の関数に意図的にstatic
を付ける場合もなくはないです(翻訳単位ごとに関数の実体が欲しい場合や、inline
キーワードのないC99より前のC言語との互換性を考慮しないといけない場合など)。
そのため、ヘッダにstatic
の付いた関数を定義しているコードが、必ずしも一概に「誤り」という訳ではありません。ただし、意図しないバグのもとになる可能性があるので特殊な事情がなければやめた方が良いです。