當然我們也可以使用偏特化技巧完成同樣的擴充,但果真如此,我們需要三個 "pair" 偏特化版本和一個主版本。Type traits 允許我們僅僅定義一個主版本,就可以自動而神奇地將自己調整為任何偏特化版,取代一一偏特化的所謂「暴力法」。以此方式使用 type traits,可允許程式員將偏特化授權(delegate)給 type traits classes,使得代碼比較容易維護,也比較容易被理解。
結論
希望這篇文章能夠給你一些想法,讓你大略知道 type-traits 是什麼。boost 說明文件中有更完整的 classes 列表,以及更進一步的使用範例。Templates 使 C++ 有能力實現泛型編程所帶來的復用性;這篇文章還告訴你,templates 可以和 generic 一樣地美好。這都有賴 type traits 帶來的價值。
致謝
感謝 Beman Dawes 和 Howard Hinnant 對本文所提的意見。
參考資料
Nathan C. Myers, C++ Report, June 1995.
這個 type traits library 的完成,要感謝 Steve Cleary, Beman Dawes, Howard Hinnant 和 John Maddock。你可以在 www.boost.org 找到它。
所謂純量型別(scalar type)就是算術型別(例如內建的整數或浮點數)、列舉型別(enumeration)、指標、函式指標、或以上任何型別再加上 const- 或 volatile- 修飾詞。
此句引自 Donald Knuth, ACM Computing Surveys, December 1974, pg 268.
這一份測試代碼是 boost utility library 的一部份(見 algo_opt_examples.cpp),以 gcc 2.95 編譯完成,所有最佳化選項都打開。我的測試結果是在 400MHz Pentium II + Microsoft Windows 98 上獲得。
John Maddock 和 Howard Hinnant 已經送出一個 "compressed_pair" library 給 Boost,其中使用的一個技術,和此處所描述的技術類似,也是用來持有 references。他們的 pair 也使用 type traits 來決定是否有任何型別是空的,並且採用 "derive" 而非 "contain" 的方式,用以保存空間 -- 這正是 "compressed" 的命名由來。
這其實是 C++ 核心語言工作小組的一個議題,由 Bjarne Stroustrup 提出。暫時的解決辦法是,允許 "a reference to a reference to T" 的意義等同於 "a reference to T",但是只能存在於 template 具現實體中,或是存在於一個「具備多個 const-volatile 修飾詞」的 method 中。
為什麼這裡不該有 const 修飾詞呢?對此感到驚訝的人,我要提醒你,請記住, references 永遠是個隱晦常數(舉個例子,你不能夠重新對一個 reference 賦值)。同時也請你記住,"const T &" 是完全不同的東西。因為這些理由,template 型別引數如果本身是個 references 的話,其「const-volatile 修飾詞」都被忽略。
泛型編程編出來的代碼,適用於任何「吻合某種條件限制」的資料型別。這已成為撰寫可復用代碼時的一個重要選擇。然而,總有一些時候,泛型不夠好 — 有時候是因為不同的型別差距過大,難以產生一致的泛化實作版本。這個時候 traits 技術就變得相當重要。這種技術可以將那些需要被納入考量的型別性質以一種 type by type 的原則,封裝於一個 traits class 內,於是可以將「由於型別之間的差異,必須撰寫出來以備用」的代碼體積降至最低,並使泛用代碼的體積提昇到最高。
Partial Specialization
A practical example
There’s nothing to prevent you from using a class template in any way you’d use an ordinary class. For example, you can easily inherit from a template, and you can create a new template that instantiates and inherits from an existing template. If the vector class does everything you want, but you’d also like it to sort itself, you can easily reuse the code and add value to it:
//: C06:Sorted.h
// Template specialization
#ifndef SORTED_H
#define SORTED_H
#include <string>
#include <vector>
template<class T>
class Sorted : public std::vector<T> {
public:
void sort();
};
template<class T>
void Sorted<T>::sort() { // A bubble sort
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(at(j-1) > at(j)) {
// Swap the two elements:
T t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
// Partial specialization for pointers:
template<class T>
class Sorted<T*> : public std::vector<T*> {
public:
void sort();
};
template<class T>
void Sorted<T*>::sort() {
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(*at(j-1) > *at(j)) {
// Swap the two elements:
T* t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
// Full specialization for char*:
template<>
void Sorted<char*>::sort() {
for(int i = size(); i > 0; i--)
for(int j = 1; j < i; j++)
if(std::strcmp(at(j-1), at(j)) > 0) {
// Swap the two elements:
char* t = at(j-1);
at(j-1) = at(j);
at(j) = t;
}
}
#endif // SORTED_H ///:~
The Sorted template imposes a restriction on all classes it is instantiated for: They must contain a > operator. In SString this is added explicitly, but in Integer the automatic type conversion operator int provides a path to the built-in > operator. When a template provides more functionality for you, the trade-off is usually that it puts more requirements on your class. Sometimes you’ll have to inherit the contained class to add the required functionality. Notice the value of using an overloaded operator here – the Integer class can rely on its underlying implementation to provide the functionality.Comment
The default Sorted template only works with objects (including objects of built-in types). However, it won’t sort pointers to objects so the partial specialization is necessary. Even then, the code generated by the partial specialization won’t sort an array of char*. To solve this, the full specialization compares the char* elements using strcmp( ) to produce the proper behavior.Comment
Here’s a test for Sorted.h that uses the unique random number generator introduced earlier in the chapter:Comment
//: C06:Sorted.cpp
// Testing template specialization
//{L} ../TestSuite/Test
//{-g++295}
//{-msc}
#include "Sorted.h"
#include "Urand.h"
#include "../arraySize.h"
#include <iostream>
using namespace std;
int main() {
Sorted<int> is;
Urand<47> rand;
for(int i = 0; i < 15; i++)
is.push_back(rand());
for(int l = 0; l < is.size(); l++)
cout << is[l] << ' ';
cout << endl;
is.sort();
for(int l = 0; l < is.size(); l++)
cout << is[l] << ' ';
cout << endl;
// Uses the template partial specialization:
Sorted<string*> ss;
for(int i = 0; i < asz(words); i++)
ss.push_back(new string(words[i]));
for(int i = 0; i < ss.size(); i++)
cout << *ss[i] << ' ';
cout << endl;
ss.sort();
for(int i = 0; i < ss.size(); i++)
cout << *ss[i] << ' ';
cout << endl;
// Uses the full char* specialization:
Sorted<char*> scp;
for(int i = 0; i < asz(words2); i++)
scp.push_back(words2[i]);
for(int i = 0; i < scp.size(); i++)
cout << scp[i] << ' ';
cout << endl;
scp.sort();
for(int i = 0; i < scp.size(); i++)
cout << scp[i] << ' ';
cout << endl;
} ///:~
Each of the template instantiations uses a different version of the template. Sorted<int> uses the “ordinary,” non-specialized template. Sorted<string*> uses the partial specialization for pointers. Lastly, Sorted<char*> uses the full specialization for char*. Note that without this full specialization, you could be fooled into thinking that things were working correctly because the words array would still sort out to “a big dog is running” since the partial specialization would end up comparing the first character of each array. However, words2 would not sort out correctly, and for the desired behavior the full specialization is necessary.Comment