Подтвердить что ты не робот

Множественное определение в файле заголовка

Учитывая этот пример кода:

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?

4b9b3361

Ответ 1

Проблема заключается в том, что следующий фрагмент кода - это определение, а не объявление:

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()", потому что он неявно inlined (любая функция-член, чье тело указана в объявлении класса, интерпретируется так, как если бы оно было объявлено "inline" ). Предохранители препроцессора предотвращают включение вашего заголовка более одного раза из одной единицы перевода ( "*.cpp" исходного файла "). Однако обе единицы перевода видят один и тот же заголовочный файл. В основном компилятор компилирует" main.cpp "в "main.o" (включая любые определения, указанные в заголовках, включенных в" main.cpp "), и компилятор отдельно компилирует" complex.cpp "в "complex.o" (включая любые определения, указанные в заголовках, включенных в" сложный ".cpp" ). Затем компоновщик объединяет "main.o" и "complex.o" в один двоичный файл, и именно в этот момент компоновщик находит два определения для функции с тем же именем. что линкер пытается разрешить внешние ссылки (например, "main.o" относится к "Complex:: Complex", но не имеет определения для этой функции... компоновщик находит определение из "complex.o" и разрешает эта ссылка).

Ответ 2

И есть ли другое решение с использованием ключевого слова inline?

Да, есть. Помимо формы, определяющей метод внутри файла реализации complex.cpp, как упоминалось другими, вы также можете поместить определение в безымянное пространство имен.

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

На практике это создаст уникальное пространство имен для каждого блока компиляции. Таким образом, вы предотвращаете столкновения имен. Однако имена все еще экспортируются из единицы компиляции, но бесполезны (поскольку имена неизвестны).

Включение определения внутри файла реализации часто является лучшим решением. Тем не менее, для шаблонов классов вы не можете этого сделать, поскольку компиляторы С++ не поддерживают создание шаблонов в модуле компиляции, отличное от того, в котором они были определены. В этом случае вы должны использовать inline или неназванное пространство имен.

Ответ 3

Переместить реализацию в complex.cpp

Сразу после включения этого файла реализация компилируется в каждый файл. Позже во время связывания там был очевидный конфликт из-за повторяющихся реализаций.

:: real() не сообщается, потому что он inline неявно (реализация внутри определения класса)

Ответ 4

У меня была эта проблема, даже после того, как мои исходники и заголовочный файл были правильными.

Оказалось, что Eclipse использовал устаревшие артефакты из предыдущей (неудачной) сборки.

Чтобы исправить, используйте Project > Clean, затем перестройте.