由於成員函數的定義全部在cpp內部,因此對其他翻譯單元不可用,函數不會被隱式地實例化,因此編譯代碼的成本僅限於單個cpp。
該方法的問題在於,您正在將模板的使用限制爲您爲其提供手動實例的類型(或類型)。外部用戶無法爲其他類型實例化,如果必須這樣做,則需要記住手動專門針對您要使用的每種類型。
還有一種方法,成本略高(不是很多),但是它比普通的方法通用且編譯速度更快。您可以提供在標題中的模板定義,但指示編譯器不隱式實例化它的一組常見的類型,然後爲它手動實例在一個單一的翻譯單元:
// .h
#ifndef TEST_H
#define TEST_H
template <typename T>
class Test
{
public:
T data;
void func() { ... } // definition here
};
extern template class Test<float>; // Declare explicit instantiation for float
extern template class Test<int>; // for int
#endif /* TEST_H */
// cpp
#include "test.h"
template class Test<float>; // explicit instantiation
template class Test<int>;
在這種方法的模板對於用戶可能想要使用的任何類型的實例,都是可見的。但是您明確告訴編譯器不要爲您提供專業化的已知類型子集進行工作。如果用戶想要Test<std::string>
那麼編譯器會隱式地實例化它,並且該翻譯單元將支付價格。對於只實例化Test<float>
,Test<int>
或包含頭文件但根本不實例化模板的翻譯單元,會有一些額外的成本(解析器需要處理定義),但不會生成代碼(二進制) ,或者優化器和/或鏈接器浪費時間來丟棄重複的符號。
正如你所提到的,它還意味着,如果頭的內容發生變化,則重新編譯包含該頭的所有用戶代碼。
在你的後一種解決方案中,在修改'func()'後,使用'Test'的單元將不會被重新編譯? –
deepmax
@MM .:構建系統很可能只關心文件的時間戳,並且仍然會重新編譯。但編譯的成本將會更小,因爲只有test.cpp會生成'Test'的代碼。如果你真的需要避免這種情況,你可以將代碼分解成一個聲明頭文件(包括特殊化聲明)和一個實現頭文件,並讓用戶選擇他們需要的東西。 –