12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++でヘッダ側に関数の実装を書くときはinlineを付けないと多重定義エラーになる

Last updated at Posted at 2020-09-01

ヘッダに関数の実装を書く場合はinlineを付けて多重定義を許容する必要あり

コンパイラが呼び出しコストを抑える目的で関数をインライン展開してくれることを期待し、短い関数の実装はあえてソースファイル側(.cpp)ではなくヘッダファイル側(.h/.hpp)に記述することがあります。

ただし、普通に書いてしまうと多重定義のリンカエラーが出てしまいます。
例えば、以下のようなヘッダファイルを書いて複数のソースファイルでインクルードするとエラーになります。
GCCであれば「multiple definition of "~"」というエラーを吐いてしまうことでしょう。

ヘッダファイル(.h/.hpp)
// ※誤り: ヘッダファイルにこう書いた場合、複数のソースファイルでインクルードすると多重定義になってしまう
int add(int a, int b)
{
    return a + b;
}

ヘッダに関数の実装を書く場合には、関数に対してinline指定子を付ける必要があります。
inline指定子を付けることで、関数が多重定義されることが許容されます。

ヘッダファイル(.h/.hpp)
// ヘッダファイルに実装を書く場合は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を付けなくても良いです。

ヘッダファイル(.h/.hpp)
class Person
{
public:
    void sayHello() const // OK: 自動的にinline扱いになる
    {
        std::cout << "Hello World!" << std::endl;
    }
};

inlineが必要となる例: メンバ関数をクラス宣言の外側に書く場合

ただし、クラス宣言の外にメンバ関数の定義を書いた場合はinlineを付けないとエラーになります。

ヘッダファイル(.h/.hpp)
class Person
{
public:
    void sayHello() const;
};

// この形式で実装をヘッダに書く場合、inlineを付けないと多重定義になる
inline void Person::sayHello() const
{
    std::cout << "Hello World!" << std::endl;
}

付けなくて良い例: 関数テンプレート

また、関数テンプレートにもinlineを付ける必要がありません。
関数テンプレートは使用時にコードが生成(インスタンス化)されるので、そもそも多重定義を禁止するルールがないためです。

ヘッダファイル(.h/.hpp)
template <typename T>
T Add(T a, T b) // 関数テンプレートは多重定義が禁止されていないので、inlineは付けなくて良い
{
    return a + b;
}

(※ただし、ヘッダ内での関数テンプレートの完全特殊化は関数テンプレートではなく関数実体となるのでinlineが必要です。)

付けなくて良い例: メンバ関数テンプレート

同じく、クラスのメンバ関数テンプレートについても、クラス宣言の外に書いた場合でもinlineを付ける必要がありません。

ヘッダファイル(.h/.hpp)
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を付け忘れることがあります。

ヘッダファイル(.h/.hpp)
// ※誤り(ヘッダファイルではなくソースファイルならこれで正解)
namespace MathUtils
{
    int Add(int a, int b) // NG: inlineを付け忘れている!
    {
        return a + b;
    }
}

ヘッダファイル内の関数にstaticを付けると一応エラーは出なくなるが誤り

ヘッダファイル内に実装した関数にstaticを付けるのは誤りです。

ヘッダファイル(.h/.hpp)
// ※誤り
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の付いた関数を定義しているコードが、必ずしも一概に「誤り」という訳ではありません。ただし、意図しないバグのもとになる可能性があるので特殊な事情がなければやめた方が良いです。

12
7
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?