質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.36%

[C++] inline関数での二重定義

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 181

score 3

下記の "void func"は、両方とも実体を持っているのか?

下記のコードをコンパイル&リンクしたところ、エラーがでずにコンパイラを通りました。

//header.hpp
inline void func();

//sub.cpp
#include"header.hpp"
void func(){
 std::cout << “sub” << std::endl;
}

//main.cpp
#include"header.hpp"
void func(){
 std::cout << “main” << std::endl;
}

int main(){
 func();
 return 0;
}


しかし、見てわかるように “void func”が2回定義されています。しかしエラーは出ませんでした。
試しに上記に書いてあるコード(.exe)を実行すると、mainの方の"void func(std::cout << main)"が実行され、subの方の"void func"(std::cout << sub) が実行されることはありませんでした。
ただ、”void func”をinlineでないようにすると、リンクエラーが起こりました。


↓下記は、同じようにコンパイルを通ったコードです。

  • headerの inline を消去し、mainの "void func" に inline を付ける。
  • headerの inline を消去し、subの "void func" に inline を付ける。
  • headerのinlineを消去し、main, sub ともに inline を付ける。
    いずれも、mainの方の "void func" のみ実行されました。

また、ファイルスコープなどの問題かもしれないと考え、「sub.cppから "void func" を呼び出す void func2関数を作成し、main関数で呼び出した」のですが、func2関数も変わらず、subの方の "void func" ではなく、mainの方の "void func" を呼び出しました。


反対に、以下の場合はエラーになりました。

  • 関数の定義を同じソースコードに2種類書く(両方ともinline指定・片方のみにinline指定共にエラー) 。

質問内容

これらのコードを踏まえて、質問は以下の通りです。

  1. "void func" は main.cpp の方のみ実体を持っているのか? それとも main.cpp も sub.cpp も共に実体を持っているが、何らかの事情で main.cpp の方しか使われないのか?
  2. sub.cpp の "void func" が使われずに、main.cpp のほうが使われるのはなぜか?
  3. なぜ上記のようなコードでも、リンクエラーにならずに、コンパイルを通るのか?(下記の「質問者の知識の程度」に記述したように、コンパイラが調整してくれるから? その調整の結果として、例えばsubの "void func" を無効化するなどしている?)

質問者の知識の程度

初心者です。私が持っている知識は、以下の通りです。

  1. inline指定をすると、その関数はinline関数になる。
  2. inline関数にすると、コンパイル時に関数の内容がコードに展開される(埋め込まれる)。
  3. inline指定は、コンパイラの判断によって展開の可否が決まる。また、inline指定をしていなくても、コンパイラが最適化の結果として、勝手にinline展開されることがある(※コンパイラの設定次第で変更可能)。
  4. inline関数はヘッダに記述することができる。その際、ODR(単一定義規則)違反にあたらないのは、inline関数の場合はコンパイラが調整してくれるため。
  5. inline関数の定義をヘッダファイルに記述するのは、inline関数の処理を展開することに関係がある。ヘッダファイルに定義を記述しないと、どういう実体をソースコードに展開すればいいのかわからないから(ソースファイル1つで完結する場合は別)。
  6. ODR違反とは、関数定義の実体が2つ以上あることで、どちらの実体を使えばいいのかわからない&実体の位置が確定しないために、問題になる。

もし知識が誤っている、正確性に問題がある場合は、ついでに正していただけると幸いです。

質問者の開発環境

  • Windows 10
  • Visual Studio Code
  • Developer Command Prompt for VS 2019
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

関数にinlineを付与すると、「全く同じ定義であれば複数のファイルに書いてもよく、すべて同じ関数として扱われる」という動きになります(これを利用して、ヘッダにinline関数の定義を書くことが可能となります)。

この例のように、同名の(staticや名前空間でスコープを違えるようなことをしていない)inline関数を、別な定義で複数作った場合、動作は未定義です(何が起きるか保証されません)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/11/25 22:52

    > ODR(単一定義規則)違反にあたらないのは、inline関数の場合はコンパイラが調整してくれるため。

    この動作が保証されるのは、複数の定義が「全く同一」の場合だけです。

    キャンセル

  • 2021/11/26 02:25

    補足:

    ODR の違反は検出しない (エラーにも警告にもならないが挙動は未定義である) ことを許す旨が明記されています。
    no diagnostic required (通例では NDR と略されます) と書かれているのがそれです。
    https://timsong-cpp.github.io/cppwp/n3337/basic.def.odr#3

    異なる翻訳単位 (translation unit) にある inline 指定された関数が同一であるかどうか (ODR に反するかどうか) を判断するにはリンク時まで待たなければなりませんし、リンク時まで関数の同一性を判断できる情報を残しておけるシステムになっているとは限りませんので検出できないこともあります。

    それと、問題を複雑にしているのは C++ の inline 関数は (デフォルトでは) 外部リンケージを持つということです。 (C では内部リンケージを持つ。) 質問の事例においては func を static inline として宣言すれば main 関数内から呼ばれる func が main.cpp 内の func であることは保証されるので、仕組みがよく理解できなければインライン関数には全部 static をつけておくという運用もありかもしれません。

    キャンセル

  • 2021/11/26 08:27

    ただしstatic lnlineは単なるstaticと変わらないのが多くの処理系だと思われます。ODR規則のため以外でinlineを使うメリットは近年殆どなくなってきました(コンパイラが無視する)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.36%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る