鉴于此代码示例:
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
关键字之外还有其他解决方案吗?
答案 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
。这也将避免多定义错误。