首页 > 代码库 > C++模板 - traits
C++模板 - traits
traits是个很好玩的东西,在泛型编程里面很常见。最早出于老外的一篇论文。http://www.cantrip.org/traits.html?seenIEPage=1 建议仔细阅读。
首先我们来看一段代码。
template<class T> T accum(const T* ptr, int len) { T total = T(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; }
这是个模板函数,很简单,就是把传进来的参数累加,返回结果。
那么这段代码有什么问题呢?
我们写几行代码来测试一下:
int sz[] = {1, 2, 3, 4, 5}; int v1 = accum(&sz[0], 5) / 5; char str[] = {'a', 'b', 'c', 'd', 'e'}; char v2 = accum(&str[0], 5) / 5;
前面两行代码把1,2,3,4,5累加然后去平均值。后面两行代码把abcde累计,然后取平均值。
运行结果如下:
v1是正确的,v2是错的(正确的应该是‘c‘。
那为什么传进去int类型的时候是对的,但是传入char是错的呢?
template<class T> T accum(const T* ptr, int len) { T total = T(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; }
我们再来看一下这个累加模板函数,注意T total = T();这一行。当const T* ptr指向int数组时,T total就是个int类型。那么12345加起来是可以存放到int类型变量里面的。
但是当ptr指向char数组时,total就是char类型,char只有一个字节8位的长度。除去符号位,一个char用来存放数字的时候就只有7位了,2的7次方 = 128,也就是最大值是127.而abcde加起来有495,所有就越界了。累加函数返回的时候就会把数据截断。结果就得到一个-3的错误平均值了。
OK, 现在我们知道这个问题的关键在于存放结果的数据类型太小了。
那么如果解决这个问题呢?
有一个简单办法
办法一:增加一个模板来指定返回值。
既然问题的关键是返回值类型太小,那么首先能想到的就是指定返回类型了。比如:
template<class T, class AccuT> AccuT accum2(const T* ptr, int len) { AccuT total = AccuT(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; }
增加一个模板参数,然后调用的时候指定返回值类型,如:
char v3 = accum2<char, int>(&str[0], 5) / 5;
这样就可以得到正确的结果了。这确实是个办法,但是不是很好,调用者每次都要指定返回值,很麻烦而且还有弄错的情况。接下来就介绍traits的方案。
办法二:traits
我们可以更一般的来考虑一下这个问题,是不是说每种传进来的参数都需要有一个对应的存放结果的类型呢?
如果每次传进来的参数都有一个对应的返回类型,那么这个问题应该就解决了。
我们考虑增加一个模板。
template<typename T> struct traits;
这个模板啥都没。然后使用模板的特化,
template<> struct traits<char> { typedef int AccuT; };
针对char来一个特化。
再来修改一下累加函数:
template<class T> typename traits<T>::AccuT accum3(const T* ptr, int len) { traits<T>::AccuT total = traits<T>::AccuT(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; }
把存放结果的类型改成traits<T>::AccuT。这个时候再来跑一下累加abcde:
char v4 = accum3(&str[0], 5) / 5;
可以得到正确的结果‘c‘了。
仔细看一下accum3,感觉好像是从类型T里面得到返回值类型。也可以理解为返回值类型是T的一个特性。这大概也就是traits这个名字的来源吧。
那如果传入一个int数组呢?
char v4 = accum3(&sz[0], 5) / 5;
编译器直接报错。那是因为没有int的traits。给int加个traits就可以了:
template<> struct traits<int> { typedef int AccuT; };
那如果再传入一个float数组呢?那编译器又报错了,因为没有float traits。这也是traits的一个好处,如果没有定义相应的traits,那么编译器直接报错,这也可以更早的发现问题。跟办法一比较,也不需要调用者自己指定返回类型了,减少了出错的可能性。还有比如传入的int数值累加值超过了int的最大值,那么直接修改int traits就行了,比如改成:
template<> struct traits<int> { typedef long long AccuT; };
其他地方都不需要修改。
总体来说traits还是很有用的,如果当碰到需要给某个类型指定一个对应类型的时候,往往可以考虑traits。STL里面的迭代器也有用到traits。
完整代码:
// ConsoleApplication1.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <memory> #include <Windows.h> #include <TlHelp32.h> #include <functional> template<class T> T accum(const T* ptr, int len) { T total = T(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; } /*****************************附加一个返回类型**********************************************/ template<class T, class AccuT> AccuT accum2(const T* ptr, int len) { AccuT total = AccuT(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; } /*****************************traits**********************************************/ template<typename T> struct traits; template<> struct traits<char> { typedef int AccuT; }; template<> struct traits<int> { typedef int AccuT; }; template<class T> typename traits<T>::AccuT accum3(const T* ptr, int len) { traits<T>::AccuT total = traits<T>::AccuT(); for (int i = 0; i < len; i++) { total += *(ptr + i); } return total; } int _tmain(int argc, _TCHAR* argv[]) { char tt = 128; int bb = tt; int sz[] = {1, 2, 3, 4, 5}; int v1 = accum(&sz[0], 5) / 5; char str[] = {'a', 'b', 'c', 'd', 'e'}; char v2 = accum(&str[0], 5) / 5; char v3 = accum2<char, int>(&str[0], 5) / 5; char v4 = accum3(&str[0], 1) / 1; return 0; }
C++模板 - traits