2010-04-28 2 views
23

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

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

Вы также можете сделать функцию статической. Встроенный спецификатор обычно используется, чтобы заставить функцию иметь внутреннюю связь. – Akanksh

+0

@Akanksh, на самом деле это именно то, для чего «inline». –

+0

@Akanksh: Использование 'static' для этой цели устарело в C++. 'static' был полностью заменен анонимными пространствами имен, хотя в этом конкретном случае' inline' - это путь. –

ответ

36

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

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

5

реализация Перейти к complex.cpp

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

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

6

And is there another solution as using the inline keyword?

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

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

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

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

+1

Это имеет серьезный недостаток, заключающийся в том, что каждая единица перевода, включая этот заголовок, получает свою собственную копию реализации. Есть ли что-то, что я пропускаю, или это действительно так глупо, как мне кажется? – sbi

+1

@sbi: это, конечно же, верно для 'inline' (и это неважно). Так что да, вы что-то упускаете. - Для простых классов размещение всего внутри файла реализации обычно лучше. Но для шаблонов у вас нет выбора (см. Обновленный ответ). –

+0

Но с 'inline' я, по крайней мере (надеюсь, я знаю) имеет то преимущество, что не вызывал вызов функции при вызове кода. В вашем примере я не вижу преимуществ, кроме того, что файл cpp не нужен. И это позаботится «inline» просто отлично. – sbi

0

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

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

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