web-dev-qa-db-ja.com

ヘッダーファイルでの複数の定義

このコードサンプルを考えると:

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キーワードを使用する以外に別の解決策はありますか?

34
Jérôme

問題は、次のコードが宣言ではなく定義であることです。

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」から定義を見つけて解決しますその参照)。

52

また、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または名前のない名前空間を使用します。

13
Konrad Rudolph

実装をcomplex.cppに移動します

このファイルをインクルードした直後は、すべてのファイルにコンパイルされています。後でリンクするときに、実装が重複しているため、明らかな競合があります。

:: real()は暗黙的にインラインであるため報告されません(クラス定義内の実装)

5
XAder

ソースとヘッダーファイルが正しい後でも、この問題が発生していました。

Eclipseが以前の(失敗した)ビルドからの古いアーティファクトを使用していることが判明しました。

修正するには、Project > Cleanその後、再構築します。

1
Paul Wintz