本节内容是作者读CPP template经典《C++ Templates》(美· David vandevoorde 德· Nicolai M·Josuttis 著)第三部分 模板和设计 第15章的笔记(也可以算读后感吧),经典不愧是经典,读来真是发人深省,今笔录之,与诸君共赏,也做CPP template的第6节-----基本操作 24/8/4
trait:类型的函数
trait 这个英文翻译成中文,是特征的涵义,我们可以从这里窥见一二它在模板中起什么作用。trait是一种CPP程序设计机制,是的,它并不是一个具体的函数,或者类什么的,它是程序专家Nathan提出的一个概念,并且Nathan把它严格化,把这种思想提交给了C++标准委员会(在Nathan的叫法中trait不叫trait,而是叫baggage template)。后来C++程序员们发现,欸,这玩意怪好用,所有trait思想的代码在CPP中很常见。下面我们一起来学习一下
在笔者看来,简单来说,trait是“类型的函数“,类比函数,输入一个值,输出一个值,类型的函数关键思想就一个”输入一个类型,就输出一个(或者多个)类型“
从累加说起
累加一个序列是相当常见的任务,如果是其他的编程语言,我们要一个个写对于不同类型的累加函数,但是对于CPP,我们下面使用模板,写到这里,程序员想着:只要有实例化,编译器就会给我们完成我们期望的”任意类型的累加函数“…是这样吗?
#include <iostream>
using namespace std;
template <typename T>
inline T accum(T const* beg, T const* end)
{
T total = T();
//T()对于内建类型总是生成0值
//注意,出问题的区域就是这里,我们用一个和输入的值的同类型的0值去存储总和,这比较危险
while (beg != end)
{
total += *beg;
++beg;
}
return total;
}
int main()
{
int num[] = { 1,2,3,4,5 };
cout << accum(&num[0], &num[5]) / 5 << endl;
char name[] = "templates";
int length = sizeof(num,name) - 1;
//逗号运算符只会计算最后一个值
cout << "length:" << length << endl;
cout << accum(&name[0], &name[length]) / length << endl;
return 0;
}
看上去很不错,对吧?但是运行后的结果呢?
3
length:9
-5
好像不太对啊(;′⌒`)。char的值总是大于0的,为什么这个字符串的平均值会是-5?很简单,发生了溢出。char的最大值是127,在累加过程中,值很轻易的超过了127,进而到达负数区(关于计算机如何存储整型的,我在前面的文章里写过了),所以你计算出来是负数。果然,有些时候想省精力,结果会搞出额外的问题。看来我们需要一个类型函数,如果输入一个类型,就给出我们希望的指定类型,比如这里,我们输入char类型,给出int类型,那累加基本就不会溢出了
Traits
下面我们来看看如何简单的使用模板来实现类型的函数
//统一模板,提供最大的匹配
template <typename T>
struct AccumTraits;
// 下面是特化的模板
// 下面我们根据匹配原则来指定元素配对
template<>
struct AccumTraits<char>
{
using ACCT = int;
};
template<>
struct AccumTraits<int>
{
using ACCT = long;
};
template<>
struct AccumTraits<unsigned int>
{
using ACCT =unsigned long;
};
template<>
struct AccumTraits<float>
{
using ACCT = double;
};
template <typename T>
inline
typename AccumTraits<T>::ACCT //返回值单写一行,typename用于告诉编译器这是类型
accum(T const* beg, T const* end)
{
using type = typename AccumTraits<T>::ACCT;//这里的typename也不能少
type total = type();
while (beg != end)
{
total += *beg;
++beg;
}
return total;
}
再运行一遍,这次就对了
3
length:9
108
value traits
也许你也注意到了,using type = typename AccumTraits<T>::ACCT;type total = type();
这个语句不总是能生成出合适的0值(type类型不是总有一个合适的默认构造函数),但是延续上面的思路,我们很容易在AccumTraits中手动添加合适的0值对象,下面以char类型为例子,那么在求和函数中,你只需要调用这个zero就行。看来模板确实能大大减少CPP程序员的代码工作量
template<>
struct AccumTraits<char>
{
using ACCT = int;
static ACCT constexpr zero = 0;
//你也可以使用一个返回0值的函数来完成这个任务
};