头文件中的多重定义

时间:2010-04-28 07:39:25

标签: c++ header-files

鉴于此代码示例:

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关键字之外还有其他解决方案吗?

5 个答案:

答案 0 :(得分:44)

问题是以下代码是定义,而不是声明:

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()”,因为它是隐式内联的(在类声明中给出其主体的任何成员函数都被解释为它已被声明为“inline”)。预处理器防护可防止您的标头被单个翻译单元(“* .cpp”源文件“)多次包含。但是,两个翻译单元都看到相同的头文件。基本上,编译器编译”main.cpp“到“main.o”(包括“main.cpp”包含的头文件中给出的任何定义),编译器分别将“complex.cpp”编译为“complex.o”(包括“complex”包含的头文件中给出的任何定义) .cpp“)。然后链接器将”main.o“和”complex.o“合并到一个二进制文件中;此时链接器为同名函数找到两个定义。它也在此处指向链接器尝试解析外部引用(例如“main.o”引用“Complex :: Complex”但没有该函数的定义...链接器从“complex.o”定位定义,并解析那个参考)。

答案 1 :(得分:9)

  

是否有另一种使用inline关键字的解决方案?

是的,有。除了其他人提到的在实现文件complex.cpp中定义方法之外,您还可以将定义放入无名空间。

namespace {
    std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
        return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
    }
}

实际上,这将为每个编译单元创建一个唯一命名空间。这样,您可以防止名称冲突。但是,名称仍然从编译单元导出但无用(因为名称未知)。

将定义放在实现文件中通常是更好的解决方案。但是,对于类模板,您不能这样做,因为C ++编译器不支持在与其定义的编译单元不同的编译单元中实例化模板。在这种情况下,您必须使用{ {1}}或未命名的命名空间。

答案 2 :(得分:5)

将实施移至complex.cpp

现在包含此文件后,正在编译实现每个文件。 稍后在链接期间,由于重复实现,存在明显的冲突。

不报告

:: real(),因为它是隐式内联的(在类定义中实现)

答案 3 :(得分:0)

即使我的源文件和头文件正确无误,我也遇到了这个问题。

事实证明,Eclipse正在使用先前(失败)构建中的陈旧工件。

要修复,请使用Project > Clean然后重建。

答案 4 :(得分:0)

在头文件中将函数定义指定为inline的另一种方法是将其定义为static。这也将避免多定义错误。