更多 C++ 習語/型別生成器
- 簡化建立複雜的基於模板的型別
- 根據模板引數合成一個或多個新型別
- 在使用基於策略的類設計時將預設策略本地化
模板化 typedef 習語
使用基於策略的類設計設計的類模板,通常會生成具有多個型別引數的非常靈活的模板。這種類模板的一個缺點是,在例項化它們時必須提供太多型別引數。在這種情況下,預設模板引數可以提供幫助。但是,當最後一個模板引數(策略類)與預設值不同時,必須指定所有中間模板引數。
例如,考慮在標準 C++ 容器中使用特殊用途分配器的情況。GNU C++ 編譯器在名稱空間__gnu_cxx中提供了許多特殊用途分配器,作為標準 C++ 庫的擴充套件。以下說明了使用 GNU 的 malloc_allocator 特化 std::map
std::map <std::string, int, less<std::string>, __gnu_cxx::malloc_allocator<std::string>>
使用float而不是int的上述 map 的變體要求再次提及所有不相關的型別引數。
std::map <std::string, float, less<std::string>, __gnu_cxx::malloc_allocator<std::string> >
型別生成器習語用於在這種情況下減少程式碼膨脹。
在型別生成器習語中,一組型別定義的公共(不變)部分被收集到一個結構中,該結構的唯一目的是生成另一個型別。例如,考慮下面顯示的Directory模板。
template <class Value>
struct Directory
{
typedef std::map <std::string, Value, std::less<std::string>,
__gnu_cxx::malloc_allocator<std::string> > type;
};
Directory<int>::type // gives a map of string to integers.
Directory<float>::type // gives a map of string to floats.
使用額外的間接級別(struct Directory)來捕獲不變部分,並留下一個或兩個模板引數用於自定義。型別生成器通常會將複雜的型別表示式合併為一個簡單的表示式。型別生成器可以透過簡單地新增更多 typedef 來用於生成多個型別。
例如,考慮如何將標準 STL 演算法應用於對映。
Directory<int>::type age; // This is a map.
transform(age.begin(), age.end(),
std::ostream_iterator<string>(std::cout, "\n"),
_Select1st<std::map<std::string, int>::value_type> ());
一個介面卡,它將 map 的 value_type(一個 pair)轉換為 pair 的第一個元素。_Select1st 在上面的示例中擔任介面卡的角色。它的型別過於複雜,在多次重複時有大量出錯的可能性。相反,型別生成器習語大大簡化了介面卡型別規範。
template <class Value>
struct Directory
{
typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
typedef _Select1st<typename type::value_type> KeySelector;
typedef _Select2nd<typename type::value_type> ValueSelector;
};
Directory<int>::type age; // This is a map.
transform(age.begin(), age.end(),
std::ostream_iterator<string>(std::cout, "\n"),
Directory<int>::KeySelector());
最後,型別生成器習語可以用於方便地更改不變型別引數(如果需要)。例如,在整個程式中將malloc_allocator更改為debug_allocator。您可能有時想要更改它的主要原因是在除錯時從邊界檢查或記憶體洩漏檢測工具中獲得更多有用的資訊。使用型別生成器,只需在一個地方更改,就可以實現這種程式範圍內的效果。
C++11 添加了using並試圖將其作為typedef的繼任者。與typedef不同,using支援模板,消除了附加::type的需要。using也更難搞砸,因為typename從不需要。<type_traits>標頭檔案從 C++14 開始就有_t和_v版本的型別特徵,所以您可以使用some_type_trait_t<Ts...>來代替typename some_type_trait<Ts...>::type。如果您之前使用舊的習語編寫了類,您也可以編寫以_t或_v結尾的輔助別名模板。
// Old code kept for backwards compatibility
template <class Value>
struct Directory
{
typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
typedef _Select1st<typename type::value_type> KeySelector;
typedef _Select2nd<typename type::value_type> ValueSelector;
};
// What you should be using instead
template <class Value>
using Directory_t = typename Directory<Value>::type;
template <class Value>
using DirectoryKeySelector = typename Directory<Value>::KeySelector;
template <class Value>
using DirectoryValueSelector = typename Directory<Value>::ValueSelector;
// Code changed to use the new types
Directory_t<int> age; // No need to remember whether typename is necessary
transform(age.begin(), age.end(),
std::ostream_iterator<string>(std::cout, "\n"),
DirectoryKeySelector<int>()); // No need to remember whether typename is necessary
- Boost.Iterator 庫
- 標頭檔案
<type_traits>
[1] 型別生成器
[2] 策略介面卡和 Boost 迭代器介面卡庫 -- David Abrahams 和 Jeremy Siek
[3] 模板 typedef -- Herb Sutter
[4] 新的 C++:typedef 模板 -- Herb Sutter