通常,當我創建一個類時,我爲該類創建一個標題和一個源。我聽說,使用模板類時,必須將函數實現放在標題中。我嘗試了兩種方式,第一種方式得到編譯錯誤。第二種方式工作正常。但是,我喜歡將代碼組織到頭文件和源文件中,因此可以將函數實現放入源文件中嗎? (也許它需要特殊的編譯標誌或語法?)或者我應該保持頭在EM?模板類成員函數的實現總是必須在C++的頭文件中進行嗎?
謝謝!
通常,當我創建一個類時,我爲該類創建一個標題和一個源。我聽說,使用模板類時,必須將函數實現放在標題中。我嘗試了兩種方式,第一種方式得到編譯錯誤。第二種方式工作正常。但是,我喜歡將代碼組織到頭文件和源文件中,因此可以將函數實現放入源文件中嗎? (也許它需要特殊的編譯標誌或語法?)或者我應該保持頭在EM?模板類成員函數的實現總是必須在C++的頭文件中進行嗎?
謝謝!
通常,所有模板代碼必須位於頭文件中,因爲編譯器需要在實例化時知道完整類型。
正如Aaron所說,可以將實現細節放在.cpp
-文件中,在這種情況下,您事先知道模板將被實例化的所有可能類型,並用這些類型顯式實例化它。如果模板在代碼中的某處被另一個類型實例化,那麼你會得到一個鏈接器錯誤。從實施至少在視覺上獨立的接口
甲相當常見的一般的解決辦法是放在一個.inc
(或.tcc
或.ipp
)-file所有實施和包括它在頭文件的末尾。
請注意,將模板類成員置於類定義之外(無論您使用特定解決方案還是一般方法)的語法稍微麻煩一些。你需要寫:
// in test.h
template <class A>
class Test
{
public:
void testFunction();
};
#include "test.inc"
// in test.inc
template <class A>
void Test<A>::testFunction()
{
// do something
}
用於此目的的常見擴展是'.tcc'(如在「模板C++」中)。 – 2011-12-28 23:54:20
Boost使用'.ipp'作爲擴展名(匹配'.hpp'作爲頭文件和'.cpp'作爲源文件)。 – ildjarn 2011-12-29 01:28:03
太棒了,這正是我需要知道的!謝謝! – 2011-12-29 08:09:03
模板實現需要在編譯時知道。這意味着實現必須對編譯器可見。沒有辦法解決這個問題。
如果您想保留實施細節的祕密,則無法執行此操作。你當然可以混淆你的代碼,但這不是很大的障礙。
如果您唯一關心的是代碼組織,您可以創建一個單獨的包含文件與實現,並將其包含在主標題的末尾(就像Andreas的建議一樣)。
你可以把模板類成員函數的實現在一個傳統的cpp文件,在某些情況下。看到我的答案。 – 2011-12-30 01:13:04
@AaronMcDaid這不是一個好主意。它違反了鬆耦合。您不能在同一模塊外擴展模板。 – 2011-12-30 06:27:14
首先,您同意可以隱藏除一個cpp文件之外的所有實現嗎?如果您知道將使用哪些模板,則不需要鬆耦合。這取決於上下文,有時候開發人員知道哪些模板將被實例化,哪些不會。 – 2011-12-30 11:26:53
(編輯:這個稍微更強大的版本,它允許執行被編譯成一個單獨的.o文件將模板應在實施CPP文件的末尾顯式實例也許這只是一個問題對於G ++)
你並不需要把實現在頭如果你知道哪些模板將被實例化,並且可以在
頭
實現文件中列出。例如,如果你知道你將只使用int
和std::string
,那麼你可以把這個頭文件:
// test.h
template <class A>
class Test
{
public:
void f();
};
,並把f()的實施成正常文件TEST.CPP:
// test.cpp
#include "test.h"
template <class A> void Test<A>::f() {
// implementation
}
template class Test<int>;
template class Test<string>;
最後兩行顯式實例化模板類。在實現文件的最後看到成員函數的實現之後,最好將它放在最後。然後你可以把它編譯成一個。o檔案g++ -c test.cpp
。該test.o文件將包含Test<int>
和Test<string>
的完整實現,並且可以在應用程序的其餘部分無任何困難地進行鏈接。
它的作品,但它是一個好主意嗎?這取決於上下文。在很多情況下,這很有效。如果您正在爲項目中的「內部」使用編寫模板,那麼您知道哪些模板將被實例化,哪些不會。但是,如果您正在向公衆提供一些必須非常靈活的東西,那麼需要將實現包含在頭文件中。
提示:即使它是公共消費,看看方法,看看是否有任何方法的參數和返回類型獨立於模板參數。如果是這樣,您可以將它們作爲(純)虛擬函數放入Base類。此基類不使用任何模板,因此您可能可以在大多數應用程序中使用此Base*
(template <class A> class Test : public Base { ...
,允許您限制整個應用程序中模板代碼的覆蓋範圍。基礎行爲和類的構造取決於模板參數,但已經構建的對象的接口不依賴於模板參數。
不知道爲什麼這是低調的,這是合理的建議,即使這些限制只能在某些情況下使用。從我+1。 – 2011-12-29 13:56:22
儘管在技術上可以將您的實現與您的界面語法的模板變得非常煩人,反覆輸入我會強烈建議你只是拿着你的鼻子,並把你的類的實現,直到你克服了氣味。
template <class X>
class klass_t {
public:
void f();
void g();
void h();
};
template <class X>
void klass_t<X>::f() { ... }
template <class X>
void klass_t<X>::g() { ... }
template <class X>
void klasS_t<X>::h() { ... }
本來有:
template <class X>
class klass_t {
public:
void f() { ... }
void g() { ... }
void h() { ... }
};
現在想象一下,你要添加其他模板參數(如以下)。 現在,您必須在n個地方更改模板參數列表,而不僅僅是一個。
template <class X, class Y>
class klass_t {
public:
void f();
void g();
void h();
};
除非你有一個很好的理由不,這是你的手指輕鬆很多隻是把一切都在類。
好點,但它與問的問題沒有多大關係。 – 2013-12-06 20:26:12
@AntonyHatchkins感謝您修復錯字。 WRT我的答案的相關性,一旦你確定某件事情是可能的,下一個值得考慮的問題往往是否是有利的。是的,可以將實現與界面與模板分開,但由於調用代碼通常依賴於實現,因此這樣做幾乎沒有優勢。我當時認爲,並且仍然認爲,解釋爲什麼你不想分離接口和實現,儘管它是可能的更有幫助。 – 2013-12-09 03:11:20
來吧,我沒有downvote你:)謝謝你的答案!我的語氣是因爲這個問題中有一個詞「標題」。在你的答案中沒有「頭」字。它製造了巨大的差異。 '技術上可能'在這裏有點過於樂觀。對於所有可以想象的(現有的和將來的)模板參數,顯式實例化的負擔比僅僅當列表增長時向模板列表添加額外參數要差得多。 – 2013-12-09 04:13:01
要回答原始問題:no,[成員]函數模板的定義不必進入標題。但是,編譯器需要查看定義來實例化模板。對於使用許多不同類型實例化的模板,您希望模板在使用時被隱式實例化。這是例如例如std::vector<...>
等類模板以及std::copy(...)
等功能模板。在這種情況下,將模板定義從聲明中分離出來幾乎是不切實際的,儘管我親自將定義放入包含在頭文件底部的單獨文件中。
對於僅使用流類或std::basic_string<...>
等幾種類型實例化的模板,通常最好將函數模板定義在單獨的類頭文件中,該文件僅包含在明確實例化它們的實現文件中。這樣,實例化模板的工作只花費一次,而不是每個使用它的翻譯單元。尤其對於流類而言,這會產生差異(主要用於編譯和鏈接時間,但在某些系統上也適用於可執行文件大小)。 ...我很確定幾乎沒有人去使用具有不同字符類型的流類別的問題,這些字符類型不同於char
和wchar_t
(提示:安排各個方面的實現和呈現方式並非是微不足道的std::locale
)。如果只有一組有限的模板用於工作,那麼明確實例化模板的技術也可以很好地工作。
您可以在.cpp文件中使用顯式實例:http://stackoverflow.com/a/2351622/14065 – 2011-12-29 00:32:55