2011-03-27 25 views
3

今天我重載< <運營商在我的一類:運算符重載C++ /在哪裏把我的代碼?

#ifndef TERMINALLOG_HH 
#define TERMINALLOG_HH 

using namespace std; 

class Terminallog { 
public: 

    Terminallog(); 
    Terminallog(int); 
    virtual ~Terminallog(); 

    template <class T> 
    Terminallog &operator<<(const T &v); 

private: 

}; 

#endif 

正如你可以看到我在頭文件中定義的重載運算符和我一起在我的.cc文件實現它:

//stripped code 

template <class T> 
Terminallog &Terminallog::operator<<(const T &v) { 
    cout << endl; 
    this->indent(); 
    cout << v; 
    return *this; 
} 

//stripped code 

後來,我用我的新類創建一個main.cpp中的文件:

#include "inc/terminallog.hh" 

int main() { 
    Terminallog clog(3); 
    clog << "bla"; 
    clog << "bla"; 
    return 0; 
} 

和我去上compilying:

g++ src/terminallog.cc inc/terminallog.hh testmain.cpp -o test -Wall -Werror 
/tmp/cckCmxai.o: In function `main': 
testmain.cpp:(.text+0x1ca): undefined reference to `Terminallog& Terminallog::operator<< <char [4]>(char const (&) [4])' 
testmain.cpp:(.text+0x1de): undefined reference to `Terminallog& Terminallog::operator<< <char [4]>(char const (&) [4])' 
collect2: ld returned 1 exit status 

BAM!一個愚蠢的鏈接器錯誤,我仍然不知道它來自哪裏。我玩了一下,注意到將我的重載操作符的實現放在我的頭文件中解決了所有問題。現在我比以前更困惑了。

爲什麼我不能在我的.cc文件中實現重載操作符?爲什麼當我把它放在我的頭文件中時它運行平穩?

困惑在此先感謝

ftiaronsem

+2

見http://stackoverflow.com/questions/3749099/why-should-the-implementation - 模板類的聲明 – Erik 2011-03-27 14:53:55

+0

鏈接器錯誤絕不是愚蠢的 - 它們只是編譯器錯誤的信息。 – quamrana 2011-03-27 14:55:04

+0

@ftiaronsem:順便說一句 - 不要在頭文件中放置'using namespace std;' - 你可以把它放在'cc'文件中,然後在頭文件中根據需要使用'std ::'限定符。 – quamrana 2011-03-27 15:01:31

回答

1

可以在cpp文件中保留實現,但是您需要爲每個正在使用的類型聲明模板的用法。有關更詳細的解釋,請參見Parashift C++ Faq

你的情況,你必須從某個地方寫一行在你的CPP文件:

template Terminallog &Terminallog::operator<<(const char* &v); 
4

編譯器必須看到的實施,能夠使用的模板。通常這意味着你把它放在標題中。

0

模板有一個特殊的屬性:他們的「執行」可作爲其簽名的一部分被認爲。

如果編譯器會在你的榜樣,看到的只是線

Terminallog &operator<<(const T &v); 

你可以使用運營商< <與絕對什麼!我的意思是蘋果和西梅。 該運算符將是全能的!

但是,您的運營商實施需要一種可以傳輸到cout的類型! 對於很多類型,這是不正確的!嘗試將你的Terminallog類放入cout。 這是行不通的。編譯器只能檢查這樣的符合性,如果你告訴它你想用T做什麼, 。

第二部分: 瞭解#include的作用。 C/C++(可惜)知道接口和實現沒有真正的區別,它沒有模塊系統。標題,cpps都只是一個慣例。你可以愉快地寫 #include <something.cpp>。什麼都不會阻止你。 #include"inc/terminallog.hh"聲明只是將標題的所有內容轉儲到當前文件(以及所有包含的東西#)中。這是爲什麼我們使用include guard a'la #ifndef TERMINALLOG_HH這個稍微令人沮喪的原因,因爲像字符串或類似的頭文件可能會被轉儲到我們的文件上百次或更多,並且編譯器會不斷拋出重定義錯誤。實際上,如果你添加了#include,它很可能會消除你的錯誤,因爲現在也會拋棄Terminallog的實現。 編譯器一個接一個地執行實現文件,包含在內,並且在一個很長的過程中運行它們。然後,它忘記了它剛纔所做的一切,然後繼續執行下一個實現文件,拉入包含文件並打開。

它首先編譯Terminallog.cc,找到所有內容的代碼,以及模板的其餘部分。它不會生成Terminallog ::運算符< <(字符串),因爲它從來沒有在那裏使用。

從部分1和2所示,當編譯器terminallog.hh後傾編譯testMain.cpp, ,這是它與合作:

class Terminallog { 
public: 

    Terminallog(); 
    Terminallog(int); 
    virtual ~Terminallog(); 

    template <class T> 
    Terminallog &operator<<(const T &v); 

private: 

}; 

int main() { 
    Terminallog clog(3); 
    clog << "bla"; 
    clog << "bla"; 
    return 0; 
} 

但從編譯器點一切似乎都很酷。運算符< <有一個模板參數 ,因此編譯器爲operator<<(T = const string), 寫入呼叫標記,對於constructor(int)完全相同。它沒有看到實施,並不在乎。你的全能運營商Voila。 它不知道在這一點上T必須有一個運營商< <與ostream!儘管如此,這是合法的,它只是希望你會提供一些暗示,以確定哪些代碼應該實際生成,如果不在此文件中,可能在下一個代碼中,也許在前一個代碼中。鏈接器會知道。編譯器只是記得「如果我找到一個函數體模板,我會用T = string爲它生成代碼」。但不幸的是,它永遠沒有機會。

然後,鏈接程序運行,替換call to constructor(int)標記,搜索它是否找到它的實現,在terminallog.cc.o中找到它。 然後它試圖找到從未生成的Terminallog::operator<<(string)。不在這裏,不在terminallog.cc.o中,沒有任何地方,並慘遭失敗。

僅當編譯器看到函數體時,纔會爲模板函數生成代碼。你可以嘗試簡單地做 Terminallog clog(3); clog < <「bla」; 在terminallog.cc中的Terminallog的構造函數中。 當編譯器看到它時,它會生成Terminallog ::運算符< <(字符串),並且鏈接器將在編譯的terminallog.cc.o目標文件中找到它。

這應該告訴你,你必須在頭文件中提供一個函數體,這樣它才能被轉儲到每一個使用它的.cc文件中,編譯器可以生成它需要的東西。 否則,你必須猜測T中可能傳入的內容,它比稍微膨脹的頭文件更令人討厭。

(有一些簡化和不準確是我寫在這裏,但它的精神應該是聲音。)