2010-04-28 12 views
23

Biorąc pod uwagę ten przykładowy kod:stwardnienie definicja w pliku nagłówkowym

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; 
} 

Podczas kompilowania tego kodu, pojawia się następujący błąd:

multiple definition of operator<<(std::ostream&, Complex const&) 

Odkryłam, że przeprowadzenie tej funkcji inline rozwiązuje problemu, ale ja nie rozumiem dlaczego. Dlaczego kompilator narzeka na wiele definicji? Mój plik nagłówkowy jest strzeżony (z #define COMPLEX_H).

A jeśli narzekają na funkcję operator<<, dlaczego nie narzekać na funkcję public real(), która jest również zdefiniowana w nagłówku?

Czy istnieje inne rozwiązanie oprócz użycia słowa kluczowego inline?

+0

Możesz również ustawić funkcję jako statyczną. Specyfikator wbudowany jest zwykle używany do wymuszenia na funkcji wewnętrznego połączenia. – Akanksh

+0

@Akanksh, właściwie to jest właśnie to, do czego służy "inline". –

+0

@Akanksh: Używanie 'static' do tego celu jest przestarzałe w C++. 'static' został całkowicie zastąpiony przez anonimowe przestrzenie nazw, chociaż w tym szczególnym przypadku,' inline' jest do zrobienia. –

Odpowiedz

36

Problem polega na tym, że następujący fragment kodu jest definicja, a nie deklaracja:

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

można zaznaczyć funkcję powyżej i sprawiają, że „inline”, tak że wiele jednostek tłumaczenia może je określić:

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

albo można po prostu przenieść oryginalna definicja funkcji do pliku źródłowego "complex.cpp".

Kompilator nie narzeka na "real()", ponieważ jest on pośrednio inline (każda funkcja członkowska, której treść podana jest w deklaracji klasy, interpretowana jest tak, jakby została zadeklarowana jako "inline"). Zabezpieczenia preprocesora zapobiegają dołączaniu nagłówka więcej niż jeden raz z pojedynczej jednostki tłumaczeniowej (plik źródłowy "* .cpp"). Jednak obie jednostki tłumaczeniowe widzą ten sam plik nagłówkowy. Zasadniczo kompilator kompiluje "main.cpp" do "main.o" (w tym wszelkie definicje podane w nagłówkach zawartych w "main.cpp"), a kompilator osobno kompiluje "complex.cpp" do "complex.o" (w tym wszelkie definicje podane w nagłówkach zawartych przez "complex .cpp "), a następnie linker łączy" main.o "i" complex.o "w pojedynczy plik binarny, w tym miejscu linker znajduje dwie definicje dla funkcji o tej samej nazwie. wskazuje, że linker próbuje rozwiązać zewnętrzne odniesienia (np. "main.o" odnosi się do "Complex :: Complex", ale nie ma definicji dla tej funkcji ... linker lokalizuje definicję z "complex.o" i rozwiązuje ten odnośnik).

5

wdrożenie Przenieś do complex.cpp

Teraz po włączeniu tej implementacji plik jest kompilowany do każdego pliku. Później podczas łączenia występuje oczywisty konflikt z powodu powielonych implementacji.

:: real() nie jest podawana, ponieważ jest inline niejawnie (wdrożenie wewnątrz definicji klasy)

6

And is there another solution as using the inline keyword?

Tak, istnieje. Oprócz zdefiniowania metody wewnątrz pliku implementacji complex.cpp, jak wspomniano przez innych, można również umieścić definicję w bezimiennym obszarze nazw.

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

W praktyce stworzy unikalny nazw dla każdej jednostki kompilacji. W ten sposób zapobiegniesz konfliktowi nazwy. Jednak nazwy są nadal eksportowane z jednostki kompilacji, ale bezużyteczne (ponieważ nazwy są nieznane).

Umieszczenie definicji w pliku implementacyjnym jest często lepszym rozwiązaniem. Jednak w przypadku szablonów klas nie można tego zrobić, ponieważ kompilatory C++ nie obsługują tworzenia szablonów w jednostce kompilacji innej niż ta, w której zostały zdefiniowane. W takim przypadku użytkownik musi użyć lub nienazwanej przestrzeni nazw.

+1

Ma to poważną wadę, że każda jednostka tłumaczeniowa zawierająca ten nagłówek otrzymuje własną kopię implementacji. Czy jest coś, czego mi brakuje, czy jest to tak głupie, jak mi się wydaje? – sbi

+1

@sbi: to samo odnosi się do 'inline' (i to nie jest wielka sprawa). Więc tak, czegoś brakuje. - W przypadku prostych zajęć umieszczanie wszystkiego w pliku implementacyjnym jest zwykle lepsze. Ale w przypadku szablonów nie masz wyboru (patrz zaktualizowana odpowiedź). –

+0

Ale z 'inline' przynajmniej (mam nadzieję, że wiem) mają tę zaletę, że nie mają wywołania funkcji podczas wywoływania kodu. Na twoim przykładzie nie widzę żadnej korzyści, z wyjątkiem faktu, że plik cpp nie jest potrzebny. I byłoby to obsługiwane przez 'inline' po prostu w porządku. – sbi

0

Wystąpił ten problem, nawet po poprawnym pliku źródłowym i nagłówkowym.

Okazało się, że Eclipse używało nieaktualnych artefaktów z poprzedniej (nieudanej) kompilacji. Aby poprawić, użyj Project > Clean, a następnie odbuduj.

Powiązane problemy