C++ template —— trait与policy类(七)

第15章 trait与policy类
------------------------------------------------------------------------------------------------------------
模板让我们可以针对多种类型对类和函数进行参数,但我们并不希望为了能够最大程度地参数化而引入太多的模板参数,同时在客户端指定所有的相应实参往往也是烦人的。我们知道我们希望引入的大多数额外参数都具有合理的缺省值。在某些情况下额外参数还可以有几个主参数来确定。
policy类和trait(或者称为trait模板)是两种C++程序设计机制。它们有助于对某些额外参数的管理,这里的额外参数是指:在具有工业强度的模板设计中所出现的参数。
trait类:提供所需要的关于模板参数的类型的所有必要信息;(STL源码大量运用了这种技巧)
policy类:有点像策略模式,通过policy类挂接不同的算法;
------------------------------------------------------------------------------------------------------------
15.1 一个实例:累加一个序列
15.1.1 fixed traits

// traits/accum1.hpp

#ifndef ACCUM_HPP
#define ACCUM_HPP

template <typename T>
inline
T accum(T const* beg, T const* end)
{
    T total  = T();      // 假设T()事实上会产生一个等于0的值
    while(beg != end)
    {
        total += *beg;
        ++beg;
    }
    return total;
}

#endif //ACCUM_HPP

考虑下面的调用过程:

// traits/accum1.cpp

#include "accum1.hpp"
#include <iostream>

int main()
{
    // 生成一个含有5个整数值的数组
    int num[] = {1,2,3,4,5};
    // 输出平均值
    std::cout << "the average value of the integer values is" << accum(&num[0], &num[5]) / 5 << ‘\n‘;

    // 创建字符值数组
    char name[] = "templates";
    int length = sizeof(name) - 1;

    // (试图)输出平均的字符值
    std::cout << "the average value of the characters in \"" << name << "\" is "
     << accum(&num[0], &num[length]) / length << ‘\n‘;
}

输出:
the average value of the integer values is 3
the average value of the characters in "templates" is -5

这里的问题是我们的模板是基于char类型进行实例化的,而char的范围是很小的,即使对于相对较小的数值进行求和也可能会出现越界的情况。显然,我们可以通过引入一个额外的模板参数AccT来解决这个问题,其中AccT描述了变量total的类型(同时也是返回类型)。然而,这将会给该模板的所有用户都强加一个额外的负担:他们每次调用这个模板的时候,都要指定这个额外的类型。因此,针对我们上面的例子,我们不得不这样编写代码:

accum<int>(&name[0], &name[length])

虽然说这个约束并不会很麻烦,但我们仍然期望可以完全避免这个约束。
关于这个额外参数,另一种解决方案是对accum()所调用的每个T类型都创建一个关联,所关联的类型就是用来存储累加和的类型。这种关联可以被看作是类型T的一个特征,因此,我们也把这个存储累加和的类型称为T的trait。于是,我们可以导出我们的第一个trait类:

// traits/accumtraits2.hpp

template<typename T>
class AccumulationTraits;

template<>
class AccumulationTraits<char>
{
    public:
        typedef int AccT;
};

template<>
class AccumulationTraits<char>
{
    public:
        typedef int AccT;
};

template<>
class AccumulationTraits<short>
{
    public:
        typedef int AccT;
};

template<>
class AccumulationTraits<int>
{
    public:
        typedef long AccT;
};

template<>
class AccumulationTraits<unsigned int>
{
    public:
        typedef unsigned long AccT;
};

template<>
class AccumulationTraits<float>
{
    public:
        typedef double AccT;
};

在上面代码中,模板AccumulationTraits被称为一个trait模板,因为它含有它的参数类型的一个trait(通常而言,可以存在多个trait和多个参数)。对这个模板,我们并不提供一个泛型的定义,因为在我们不知道参数类型的前提下,并不能确定应该选择什么样的类型作为和的类型。然而,我们可以利用某个实参类型,而T本身通常都能够作为这样的一个候选类型。这样,我们可以改写前面的accum()模板如下:

// traits/accum2.hpp

#ifndef ACCUM_HPP
#define ACCUM_HPP

template<typename T>
inline
typename AccumulationTraits<T>::AccT accum(T const* beg, T const* end)
{
    // 返回值的类型是一个元素类型的trait
    typedef typename AccumulationTraits<T>::AccT Acct;

    AccT total = AccT();        // 假设AccT()实际上生成了一个0值
    while(beg != end)
    {
        total += *beg;
        ++beg;
    }
    return total;
}

#endif     //  ACCUM_HPP

// 于是,现在例子程序的输入完全符合我们的期望,如下:
the average value of the integer values is 3
the average value of the characters in "templates" is 108

15.1.2 value trait
到目前为止,我们已经看到了trait可以用来表示:“主”类型所关联的一些额外的类型信息。在这一小节里,我们将阐明这个额外的信息并不局限于类型,常数和其他类型的值也可以和一个类型进行关联。
我们前面的accum()模板使用了缺省构造函数的返回值来初始化结果变量(即total),而且我们期望该返回值是一个类似0的值:

AccT total = AccT();       // 假设AccT()实际上生成了一个0值
...
return total;

显然,我们并不能保证上面的构造函数会返回一个符合条件的值,可以用来开始这个求和循环。而且,类型AccT也不一定具有一个缺省构造函数。
在此,我们可以再次使用trait来解决这个问题。对于上面的例子,我们需要给AccumulationTraits添加一个value trait,最终选择的方案如下(书籍介绍了最后选择这种方案的原因,在此省略,详见书籍):

// traits/accumtraits4.hpp

template<typename T>
class AccumulationTraits;

template<>
class AccumulationTraits<char>
{
    public:
        typedef int AccT;
    // 之所以选择使用静态函数返回一个值,原因如下:
    // 方案1:直接定义“static AccT const zero = 0;”,缺点:在所在类的内部,C++只允许我们对整型和枚举类型初始化成静态成员变量
    // 方案2:类内声明“static double const zero;”,源文件进行初始化“double const AccumulationTraits<float>::zero = 0.0;”,    缺点:这种解决方法对编译器而言是不可知的。也就是说,在处理客户端文件的时候,编译器通常都不会知道位于其他文件的定义
    // 综上,选择了下面使用静态函数返回所需要的值的方法
    static AccT zero(){
        return 0;
    }
};
// 其他内建类型的特化版本类似
......

对于应用程序代码而言,唯一的区别只是这里使用了函数调用语法(而不是访问一个静态数据成员):

AccT total = AccumulationTraits<T>::zero(); 

显然,trait还可以代表更多的类型。在我们的例子中,trait可以是一个机制,用于提供accum()所需要的、关于元素类型的所有必要信息;实际上,这个元素类型就是调用accum()的类型,即模板参数的类型。下面是trait概念的关键部分:trait提供了一种配置具体元素(通常是类型)的途径,而该途径主要是用于泛型计算。

在上一节所使用的trait被称为fixed trait,因为一旦定义了这个分离的trait,就不能再算法中对它进行改写。然而,在有些情况下我们需要对trait进行改写。从原则上讲,参数化trait主要的目的在于:添加一个具有缺省值的模板参数,而且该缺省值是由我们前面介绍的trait模板决定的。在这种具有缺省值的情况下,许多用户就可以不需要提供这个额外的模板实参;但对于有特殊需求的用户,也可以改写这个预设的类型。

对于这个特殊的解决方案,唯一的不足在于:我们并不能对函数模板预设缺省模板实参。可以通过把算法实现为一个类,绕过这个不足。这同时也说明了:除了函数模板之外,在类模板中也可以很容易地使用trait,唯一的确点就是:类模板不能对它的模板参数进行演绎,而是必须显式提供这些模板参数。因此,我们需要编写如下形式的代码: Accum<char>::accum(&name[0], &name[length]) 前面例子的代码修改如下:

#ifndef ACCUM_HPP
#define ACCUM_HPP

#include "accumtraits4.hpp"

template <typename T,
                typename AT = AccumulationTraits<T> >
class Accum
{
    public:
        static typename AT::AccT accum(T const* beg, T const* end)
        {
            typename AT::AccT total = AT::zero();
            while (beg != end)
            {
                total += *beg;
                ++beg;
            }
            return total;
        }
};
#endif   // ACCUM_HPP

通常而言,大多数使用这个模板的用户都不必显式地提供第2个模板实参,因为我们可以针对第1个实参的类型,为每种类型都配置一个合适的缺省值。
和大多数情况一样,我们可以引入一个辅助函数,来简化上面基于类的接口:

template<typename T>
inline
typename AccumulationTraits<T>::AccT accum(T const* beg, T const* end)
{
    // 第2个实参由类模板的缺省实参提供
    return Accum<T>::accum(beg, end);
}

template<typename Traits, typename T>
inline
typename Traits<T>::AccT accum(T const* beg, T const* end)
{
    // 第2个实参由Traits实参提供,替换缺省实参
    return Accum<T, Traits>::accum(beg, end);
}

15.1.4 policy 和 policy类(个人理解:有点像策略模式,挂接核心操作,改变算法行为)

到目前为止,我们把累积(accumulation)与求和(summation)等价起来了。事实上,还可以有其他种类的累积。例如,我们可以对序列中的给定值进行求积;如果这些值是字符串的话,还可以对它们进行连接。甚至于在一个序列中找到一个最大值,也可以被看成是累积问题的一种形式。在这所有的情况中,针对accum()的所有操作,唯一需要改变的只是“total += *beg;” 操作。于是,我们就把这个操作称为该累积过程的一个policy。因此,一个policy类就是一个提供了一个接口的类,该接口能够在算法中应用一个或多个policy。(个人理解:policy,核心操作的一个代理,通过替换policy,达到改变算法核心操作,从而改变算法行为的目的)

下面是一个例子,它说明了如何在我们的Accum类模板中引入这样的一个接口:

// traits/accum6.hpp

#ifndef ACCUM_HPP
#define ACCUM_HPP

#include "accumtraits4.hpp"
#include "sumpolicy1.hpp"

template <typename T,
                typename Policy = SumPolicy,
                typename Traits = AccumulationTraits<T> >
class Accum
{
    public:
        typedef typename Traits::AccT AccT;
        static AccT accum(T const* beg, T const* end)
        {
            AccT total = Traits::zero();
            while (beg != end)
            {
                Policy::accumulate(total, *beg);
                ++beg;
            }
            return total;
        }
};
#endif //ACCUM_HPP

其中SumPolicy类可以编写如下:

// traits/sumpolicy1.hpp
#ifndef SUMPOLICY_HPP
#define SUMPOLICY_HPP

class SumPolicy
{
    public:
        template<typename T1, typename T2>  // 成员模板
        static void accumulate(T1& total, T2 const & value)
        {
            total += value;
        }
};
#endif //SUMPOLICY_HPP

在这个例子中,我们把policy实现为一个具有一个成员函数模板的普通类(也就是说,类本身不是模板,而且该成员函数是隐式内联的)。后面我们还会讨论另一种实现方案。
通过给累积值指定一个不同的policy,我们就可以进行不同的计算。如下:

// traits/accum7.cpp
#include "accum6.hpp"
#include <iostream>

class MultiPolicy
{
    public:
        template<typename T1, typename T2>
        static void accumulate(T1& total, T2 const & value){
            total *= value;
        }
};

int main()
{
    // 创建含有具有5个整型值的数组
    int num[] = {1, 2, 3, 4, 5};
    // 输出所有值的乘积
    std::cout << "the product of the integer values is " << Accum<int, MultiPolicy>::accum(&num[0], &num[5]) << ‘\n‘;
}

15.1.3 参数化trait

时间: 2024-12-22 01:26:13

C++ template —— trait与policy类(七)的相关文章

C++模板 - policy类

一讲到traits,相应的就会联系到policy.那么policy是干啥的呢? 看一下下面的累加代码. template<class T, class P> typename traits<T>::AccuT accum(const T* ptr, int len) { traits<T>::AccuT total = traits<T>::Zero(); for (int i = 0; i < len; i++) { total += *(ptr +

c++ template 判断是否为类类型

/* The following code example is taken from the book * "C++ Templates - The Complete Guide" * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002 * * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002. * Permission to co

轻松把玩HttpClient之封装HttpClient工具类(七),新增验证码识别功能

这个HttpClientUtil工具类分享在GitHub上已经半年多的时间了,并且得到了不小的关注,有25颗star,被fork了38次.有了大家的鼓励,工具类一直也在完善中.最近比较忙,两个多月前的修改在今天刚修改测试完成,今天再次分享给大家. 验证码识别这项技术并不是本工具类的功能,而是通过一个开源的api来识别验证码的.这里做了一个简单的封装,主要是用来解决登陆时的验证码的问题.在线验证码识别官网:http://lab.ocrking.com/,github地址:https://githu

Python类(七)-类的特殊成员方法

__doc__ 用来表示类的描述信息 # -*- coding:utf-8 -*- __author__ = "MuT6 Sch01aR" class Person(object): '''这个类是用来描述人的''' def __init__(self,name,age): self.name = name self.age =age if __name__ == '__main__': p = Person('John',22) print(p.__doc__) 运行结果 __cla

c++学习书籍推荐《C++ Templates》下载

详细讲解C++模板语言的概念. 使用C++模板的常用设计技巧. 应用例证(其中一些是"高 级"应用). 百度云及其他网盘下载地址:点我 名人推荐 如果今年你只打算买一本C++的书,那就选<C++Templates中文版>吧! --JoshWalker,ACCU成员 <C++Templates中文版>是C++世界期待已久的重量级著作. --MarcBriand,C/C++UsersJournal前任总编 一句话,模板圣经已经写就,请研习. --MaximKhesi

C++ template —— 智能指针(十二)

在管理动态分配的内存时,一个最棘手的问题就是决定何时释放这些内存,而智能指针就是用来简化内存管理的编程方式.智能指针一般有独占和共享两种所有权模型.------------------------------------------------------------------------------------------------------------20.1 holder和trule本节将介绍两种智能指针类型:holder类型独占一个对象:而trule可以使对象的拥有者从一个hold

模板拾遗二_traits和policy

1,traits char name[] = "templates"; int legnth = sizeof(name) - 1; accum(&name[0], &name[legnth]); 2,value trait value trait有个缺点:在所在类的内部,C++只允许对整形和枚举类型初始化成静态成员变量.显然folat或其它自定义类型等不能使用上面解决方案. 3,使用静态成员函数来做为value trait 4,trait提供了一种配置具体元素(通常是

七牛上传凭证怎样自己写

Qiniu 七牛问题解答 很多人反应上传凭证自己书写的时候出现很多问题.这里我给大家一个范例 问题解决方案 此段代码为c#案例,生成token ak,sk需要创建七牛账户,并在空间秘钥中获取. public String getToken() { Policy policy = new Policy("liuhanlin-work"); // policy.Deadline=1390528576; // policy.PersistentNotifyUrl="";

快学Scala 第二十一课 (初始化trait的抽象字段)

初始化trait的抽象字段: trait Logged { println("Logged constructor") def log(msg: String){ println("Logged")} } trait FileLogger extends Logged { var filename: String override def log(msg: String) { println("filename:" + filename) } }