头文件中的多重定义

Jér*_*ôme 31 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
Run Code Online (Sandbox Code Playgroud)

complex.cpp:

#include "complex.h"

Complex::Complex(float Real, float Imaginary) {
   m_Real = Real;
   m_Imaginary = Imaginary;
}
Run Code Online (Sandbox Code Playgroud)

main.cpp:

#include "complex.h"
#include <iostream>

int main()
{
   Complex Foo(3.4, 4.5);
   std::cout << Foo << "\n";
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译此代码时,我收到以下错误:

multiple definition of operator<<(std::ostream&, Complex const&)
Run Code Online (Sandbox Code Playgroud)

我发现使这个功能inline解决了这个问题,但我不明白为什么.为什么编译器会抱怨多重定义?我的头文件被保护(带#define COMPLEX_H).

并且,如果抱怨operator<<函数,为什么不抱怨public real()函数,这也在标题中定义?

除了使用inline关键字之外还有其他解决方案吗?

Mic*_*yan 50

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

std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
   return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
Run Code Online (Sandbox Code Playgroud)

您可以标记上面的函数并使其"内联",以便多个翻译单元可以定义它:

inline std::ostream& operator<<(std::ostream& o, const Complex& Cplx) {
   return o << Cplx.m_Real << " i" << Cplx.m_Imaginary;
}
Run Code Online (Sandbox Code Playgroud)

或者,您只需将函数的原始定义移动到"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"定位定义,并解析那个参考).


Kon*_*lph 12

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

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

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

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

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

  • @sbi:对于`inline`来说当然也是如此(这没什么大不了的).所以是的,你错过了一些东西. - 对于简单的类,将所有内容放在实现文件中通常会更好.但对于模板,您没有选择(请参阅更新的答案). (2认同)

XAd*_*der 5

将实现移至complex.cpp

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

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