このコードサンプルを考えると:
complex.h:
_#ifndef COMPLEX_H
#define COMPLEX_H
#include <iostream>
class Complex
{
public:
Complex(float Real, float Imaginary);
float real() const { return m_Real; };
private:
friend std::ostream& operator<<(std::ostream& o, const Complex& Cplx);
float m_Real;
float m_Imaginary;
};
std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
#endif // COMPLEX_H
_
complex.cpp:
_#include "complex.h"
Complex::Complex(float Real, float Imaginary) {
m_Real = Real;
m_Imaginary = Imaginary;
}
_
main.cpp:
_#include "complex.h"
#include <iostream>
int main()
{
Complex Foo(3.4, 4.5);
std::cout << Foo << "\n";
return 0;
}
_
このコードをコンパイルすると、次のエラーが発生します。
_multiple definition of operator<<(std::ostream&, Complex const&)
_
この関数をinline
にすると問題が解決することがわかりましたが、その理由がわかりません。コンパイラが複数の定義について文句を言うのはなぜですか?私のヘッダーファイルは保護されています(_#define COMPLEX_H
_を使用)。
そして、_operator<<
_関数について不平を言う場合は、ヘッダーでも定義されているpublic real()
関数について不平を言わないのはなぜですか?
そして、inline
キーワードを使用する以外に別の解決策はありますか?
問題は、次のコードが宣言ではなく定義であることです。
std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
上記の関数をマークして「インライン」にして、複数の翻訳単位で定義できるようにすることができます。
inline std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
または、関数の元の定義を「complex.cpp」ソースファイルに移動するだけです。
「real()」は暗黙的にインライン化されているため、コンパイラーは「real()」について文句を言いません(クラス宣言で本体が指定されているメンバー関数は、「インライン」と宣言されたかのように解釈されます)。プリプロセッサガードは、単一の変換単位( "* .cpp"ソースファイル ")からヘッダーが複数回含まれるのを防ぎます。ただし、両方の変換単位は同じヘッダーファイルを参照します。基本的に、コンパイラは" main.cpp "をコンパイルして"main.o"( "main.cpp"に含まれるヘッダーで指定された定義を含む)、およびコンパイラは "complex.cpp"を "complex.o"に個別にコンパイルします( "complex。 .cpp ")。次に、リンカは" main.o "と" complex.o "を1つのバイナリファイルにマージします。この時点で、リンカは同じ名前の関数の2つの定義を見つけます。リンカが外部参照を解決しようとする点(たとえば、「main.o」は「Complex :: Complex」を参照しますが、その関数の定義がありません...リンカは「complex.o」から定義を見つけて解決しますその参照)。
また、
inline
キーワードを使用する別の解決策はありますか?
はいあります。実装ファイル内のメソッドを定義する別のフォームcomplex.cpp
他の人が述べたように、定義を名前のない名前空間に置くこともできます。
namespace {
std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
}
実際には、これは各コンパイル単位に対してunique名前空間を作成します。そうすれば、名前の衝突を防ぐことができます。ただし、名前はコンパイル単位からエクスポートされますが、役に立たない(名前が不明であるため)。
多くの場合、定義を実装ファイル内に置くことは、より良い解決策です。ただし、クラステンプレートの場合、C++コンパイラは、テンプレートが定義されたものとは異なるコンパイル単位でのテンプレートのインスタンス化をサポートしていないため、これを行うことはできません。その場合、する必要がありますinline
または名前のない名前空間を使用します。
実装をcomplex.cppに移動します
このファイルをインクルードした直後は、すべてのファイルにコンパイルされています。後でリンクするときに、実装が重複しているため、明らかな競合があります。
:: real()は暗黙的にインラインであるため報告されません(クラス定義内の実装)
ソースとヘッダーファイルが正しい後でも、この問題が発生していました。
Eclipseが以前の(失敗した)ビルドからの古いアーティファクトを使用していることが判明しました。
修正するには、Project > Clean
その後、再構築します。