文章来自Traits: a new and useful template technique
应该是很老(1995)的文章了,不过很适合作为Template入门的材料。
ANSI/ISO C++标准库一开始就想支持国际化(internationalization),虽然一开始还没相好具体细节,但是最近5年逐渐有点眉目了。现在得到的结论是,应当用template来对需要进行字符操作的工具进行参数化。
给现有的iostream和string类型进行参数化其实挺难的,需要发明一种新的技术才行。幸运的是,这种技术可以很好的服用在其他地方。
问题
在iostream中,streambuf需要一个EOF来完成自身的功能。在一般的库中,EOF就是一个int而已;一些需要读取字符的函数也只返回int数值。
class streambuf {
...
int sgetc(); // return the next character, or EOF.
int sgetn(char*, int N); // get N characters.
};
但如果用一种字符类型来参数化streambuf,会发生什么?一般来说,程序并不需要这个类型,而是需要这个类型的EOF的值。先试试看:
template
class basic_streambuf {
...
intT sgetc();
int sgetn(charT*, int N);
};
这个多出来的intT就有点烦人了。库的使用方才不关心所谓的EOF是啥。问题还不只这个,sgetc在遇到EOF时应该返回什么?莫非要再来一个模板参数?看来这种方式行不通。
Traits技法
这时候就需要引入Traits了。有了traits,就不需要从原来的模板里直接取参数,而是定义一个新的模板。用户一般不会直接使用这个新的模板,所以这个模板名字可以起的人性化一点。
template
struct ios_char_traits { };
默认的traits是个空类。对于真实的字符类型,可以特化这个模板以提供有意义的语义。
struct ios_char_traits {
typedef char char_type;
typedef int int_type;
static inline int_type eof() { return EOF; }
};
ios_char_traits
template
class basic_streambuf {
public:
typedef ios_char_traits traits_type;
typedef traits_type::int_type int_type;
int_type eof() { return traits_type::eof(); }
...
int_type sgetc();
int sgetn(charT*, int N);
};
除去typedef,这个和最开始的定义很像。现在就有1个模板参数,也是用户需要关心的唯一一个模板参数。编译器会从这个trait类中寻找需要的信息。除了一些变量的声明需要调整,用户的使用代码和之前看起来没有太大不同。
如果streambuf用了另外一个字符类型,这时只要重新特化ios_char_traits即可。要支持wchar_t可以这么写:
struct ios_char_traits {
typedef wchar_t char_type;
typedef wint_t int_type;
static inline int_type eof() { return WEOF; }
};
string类可以用同样的方式参数化。
这个技巧的适用场景:1. 对原始类型进行模板参数化;2.对没办法改动的类进行定制
另一个例子
更进一步解释之前,再看看另一个例子(来自ANSI/ISO C++ [Draft] Standard)。
有一个数值计算库使用的类型有float, double和long double,每个类型都有关联的"epsilon"、指数和底数。这些数值在
template
struct float_traits { };
struct float_traits {
typedef float float_type;
enum { max_exponent = FLT_MAX_EXP };
static inline float_type epsilon() { return FLT_EPSILON; }
...
};
struct float_traits {
typedef double float_type;
enum { max_exponent = DBL_MAX_EXP };
static inline float_type epsilon() { return DBL_EPSILON; }
...
};
现在可以在不知道具体类型(float/double/long double)直接取到max_exponent。举个matrix的例子
template
class matrix {
public:
typedef numT num_type;
typedef float_traits traits_type;
inline num_type epsilon() { return traits_type::epsilon(); }
...
};
到现在为止的例子里,每个模板参数都有一系列public的typedef,而使用这些参数的类都强依赖于这些typedef。这绝非偶然:大多数的情况下,作为参数的traits必须提供public的typedef,使用这些traits的template才能正确的实例化。
学到一点:一定要提供这些public的typedef。
默认模板参数
到1993年为止,编译器就可以支持上述的用法。1993年11月后,一个更好的方案呼之欲出:可以制定默认的模板参数。当下已经有不少编译器支持数值作为默认模板参数了,新方案更进一步,允许类型作为默认模板参数。
Stroustrup's Design and Evolution of C++ (page 359)有一个示例。首先定义一个traits: CMP,和一个简单的参数化的string。
template class CMP {
static bool eq(T a, T b) { return a == b; }
static bool lt(T a, T b) { return a < b; }
};
template class basic_string;
这时就可以为string自定义compare操作了:
template >
int compare(const basic_string&,
const basic_string&);
这里不讨论具体实现细节,但需要关注第二个模板参数。首先,这个C不仅仅是class,而且是实例化后的class。其次,第二个模板参数(C)自己也需要参数,而需要的参数是compare的第一个模板参数(charT)。这在函数声明中是不可以的,但在模板声明时可行。
这种方式允许用户可以自定义比较的过程。把这个技术应用在我们自己的streambuf模板上看下:
template >
class basic_streambuf {
public:
typedef traits traits_type;
typedef traits_type::int_type int_type;
int_type eof() { return traits_type::eof(); }
...
int_type sgetc();
int sgetn(charT*, int N);
};
这给了我们为特定char定制traits的机会。这对库的用户来说很重要,因为EOF在不同的字符集映射中是有可能不一样的。
运行时的Traits
更进一步,看戏streambuf的构造函数:
template >
class basic_streambuf {
traits traits_; // member data
...
public:
basic_streambuf(const traits& b = traits())
: traits_(b) { ... }
int_type eof() { return traits_.eof(); }
};
现在我们traits也可以在运行时发挥作用了,而不仅仅是编译时。在这个例子中,traits_.eof()可能是一个静态函数,或者是一个普通的成员函数。如果是普通成员函数,eof()可能会用到构造traits时的一些参数。(这个技巧是有实际使用场景的,比如标准库里的容器都有的allocator)
值得注意的是,对使用方来说,现在没有任何的改变,默认参数可以满足大部分的使用需求。但是当有自己特殊需求时,现在的模板定义也能提供修改的机会。不论什么情况下,我们都会生成最优的代码,如果不需要额外的代价,我们就不会引入这些额外的代价!
总结
只要编译器支持template,traits技巧就可以直接上手用起来了。
Traits可以将相关联的类型、值、函数等用模板参数关联起来,同时不引入过多的噪声。这项语言特性(默认模板参数)极大的扩充了语言能力,提供了足够的灵活性,也丝毫不损害运行效率。
参考
- Stroustrup, B. Design and Evolution of C++, Addison-Wesley, Reading, MA, 1993.
- Barton, J.J., and L.R. Nackman, Scientific and Engineering C++, Addison-Wesley, Reading, MA, 1994.
- Veldhuizen, T. " Expression Templates", C++ Report, June 1995, SIGS Publications, New York.