C++
1つの定義ルール(ODR)
サーチ…
乗算された関数を定義する
1つの定義ルールの最も重要な結果は、外部リンケージを持つ非インライン関数は、複数回宣言することはできますが、プログラム内で1回のみ定義することです。したがって、異なる翻訳単位から複数のヘッダを含めることができるため、このような関数をヘッダに定義するべきではありません。
foo.h
:
#ifndef FOO_H
#define FOO_H
#include <iostream>
void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp
:
#include "foo.h"
void bar() { std:: cout << "bar"; }
main.cpp
:
#include "foo.h"
int main() {
foo();
bar();
}
このプログラムでは、関数foo
はヘッダーfoo.h
定義されていますfoo.h
はfoo.cpp
から1回、 main.cpp
から2回main.cpp
ます。したがって、各翻訳単位には独自のfoo
の定義が含まれています。 foo.h
のインクルードガードは、 foo.cpp
とmain.cpp
両方にfoo.h
別々にmain.cpp
れているので、これを防ぐものではないことに注意してください。このプログラムをビルドしようとすると、 foo
が複数定義されていると特定するリンク時エラーが発生する可能性が最も高いです。
このようなエラーを回避するには、ヘッダーに関数を宣言し、対応する.cpp
ファイルで定義する必要があります(他の例を参照)。
インライン関数
inline
宣言された関数は、すべての定義が同一であれば、複数の翻訳単位で定義することができます。また、それが使用されているすべての翻訳単位で定義する必要があります。したがって、インライン関数はヘッダーに定義する必要があり、実装ファイルに記述する必要はありません。
プログラムは、関数の単一の定義があるかのように動作します。
foo.h
:
#ifndef FOO_H
#define FOO_H
#include <iostream>
inline void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp
:
#include "foo.h"
void bar() {
// more complicated definition
}
main.cpp
:
#include "foo.h"
int main() {
foo();
bar();
}
この例では、より単純な関数foo
はヘッダーファイルでインラインで定義され、より複雑な関数bar
はインラインではなく、実装ファイルで定義されています。 foo.cpp
とmain.cpp
両方の翻訳単位にはfoo
定義が含まれていますが、このプログラムはfoo
がインラインであるため適切な形式です。
クラス定義内で定義された関数(メンバ関数またはフレンド関数かもしれない)は暗黙的にインラインです。したがって、クラスがヘッダに定義されている場合、定義が複数の翻訳単位に含まれていても、そのクラスのメンバ関数をクラス定義内に定義することができます。
// in foo.h
class Foo {
void bar() { std::cout << "bar"; }
void baz();
};
// in foo.cpp
void Foo::baz() {
// definition
}
関数Foo::baz
は行外で定義されているため 、インライン関数ではないので、ヘッダに定義してはいけません。
オーバーロード解決によるODR違反
インライン関数のトークンが同じであっても、名前の参照が同じエンティティを参照していない場合、ODRに違反する可能性があります。次のようにfunc
を考えてみましょう:
header.h
void overloaded(int); inline void func() { overloaded('*'); }
foo.cpp
#include "header.h" void foo() { func(); // `overloaded` refers to `void overloaded(int)` }
bar.cpp
void overloaded(char); // can come from other include #include "header.h" void bar() { func(); // `overloaded` refers to `void overloaded(char)` }
overloaded
れたODR違反は、翻訳単位に応じて異なるエンティティを参照しています。