条款47 请使用traits classes表现类型信息

记住:

★Traits classes使得“类型相关信息”在编译期可用。它们以templates和“templates特化”完成实现

★整合重载技术后,traits classes有可能在编译期对类型执行if...else测试

---------------------------------------------------------

STL共有5种迭代器:

①Input迭代器: 如:istream_iterator

  仅前移,一次一步;

  客户只可读取,且只能读取一次;

②Output迭代器:如ostream_iterator

  仅前移,一次一步;

  客户只可涂写其所指物,且只能涂写一次,不能读取

③Forward迭代器:

  只能使用++操作符来单向遍历容器(不能用--);

  可读写;

④Bidirectional迭代器:如STL的list、set、map的迭代器

  可以用++和--操作符来双向遍历容器;

  可读写;

⑤Random access迭代器: 如vector、deque和string的迭代器

  可执行“迭代器算术”;

  可直接访问容器中的任意一个元素的双向迭代器;

对于这五种分类,C++标准库提供专门的类型标记结构对它们进行区分:

    struct input_iterator_tag{};

  struct output_iterator_tag{};

  struct forward_iterator_tag : public input_iterator_tag{};

  struct bidirectional_iterator_tag : public forward_iterator_tag{};

  struct random_access_iterator_tag : public bidirectional_iterator_tag{};

------------------------------

Traits并非C++关键字或一个预先定义好的构件;它是一种技术,也是一个C++程序员共同遵守的协议。

------------------------------

设计并实现一个traits class

使用traits技术可以在编译期间获取某些类型信息,它要求对内置类型和用户自定义类型表现得一样好。标准模板库是把traits信息放到模板中,其中针对迭代器的被命名为iterator_traits,它是一个结构体:

    template<typename IterT>

    struct iterator_traits;

其工作原理是:针对每一个类型IterT,在结构体中声明某个typedef名为iterator_category,这个typedef用于确认IterT的迭代器分类。它包括两部分实现:

(1)对于自定义类型,用户必须在类模板中声明一个typedef名为iterator_category,比如对双端队列deque和列表list的模板类:

template<typename ...>
class deque{
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
...
};
...
}; template<typename ...>
class list{
public:
class iterator{
public:
typedef bidirectional_access_iterator_tag iterator_category;
...
};
...
};

在iterator_traits中,获取迭代器的类型信息:

template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;
...
};

(2)对于内置类型的指针,iterator_traits提供一个偏特化版本:

template<typename IterT>         //template偏特化
struct iterator_traits<IterT*>{ //针对内置指针
typedef random_access_iterator_tag iterator_category;
...
};

因此,设计并实现traits class的步骤是:

(1)确认若干希望将来可取得的类型相关信息(例如对于迭代器,希望获取它的分类)。

(2)为该信息选择一个名称(例如iterator_category)。

(3)提供一个模板和一组特化版本(如iterator_traits),内含希望支持的类型相关信息。

-------------------------------------

traits class的使用

如上所述的类型信息可在编译期间获取,但是此时需要对传递来的指针类型做出判断,如果使用if...else...分支判断语句,只有在运行期间才能获取。怎么办?

可以使用函数重载的办法:重载函数需要在编译期间通过确定参数类型来决定调用哪个版本,正好可以解决“编译期条件句”的问题。

PS:这个想法实在太牛了!

如前所述的迭代器类型问题,可以设计多个重载函数做适配于各个类型的操作,然后在同一个函数中调用它们。

template<typename IterT, typename DistT>
void advance( IterT& iter, DistT d ) { doAdvance( //doAdvance()有多个重载,其版本不同之处在于传递的迭代器的参数不同
iter, d,
typename std::iterator_traits<IterT>::iterator_category()
);
..
}; template<typename IterT, typename DistT> //这份实现用于random access迭代器
void doAdvance( IterT& iter, DistT d, std::random_access_iterator_tag )
{
iter+=d;
} template<typename IterT, typename DistT> //这份实现用于bidirectional迭代器
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)
{
if(d>=){
while(d--)
++iter;
}
else{
while(d++)
--iter;
}
} template<typename IterT, typename DistT> //这份实现用于input迭代器,由于forward迭代器派生于input迭代器,也可用于forward迭代器
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
if(d<){
throw std::out_out_range("Negnative distance"); //对于input或forward迭代器,移动负距离会导致不明确的行为,因而抛出异常
}
while(d--)
++iter;
}

可见,使用traits class的方法是:

(1)建立一组重载函数或函数模板,彼此间差异只在于各自的traits参数,令每个函数实现代码与其接受的traits信息相适配。

(2)建立一个控制函数或函数模板,它调用上述重载函数并传递traits class所提供的信息。

条款48 认识template元编程(TMP

记住:

★TMP(模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率

★TMP可被用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码

--------------------------------------------------------------------------------------------------

TMP可以将部分执行期的任务提前至编译期完成,从而可以更早发现错误,更高效(编译时间会变长,但是执行期效率会高)。且TMP对“难以或甚至不可能于运行期实现出来的行为”的表现也很吸引人!

----------------------------------------------

TMP是图灵完备的,可以执行分支语句和循环(通过递归实现)。如解决阶乘问题:

template<unsigned n>        //一般情况,递推关系式
struct Factorial{
enum { value = n * Factorial<n->::value };
};
template<> //特殊情况,以结束递归
struct Factorial<>{
enum{value = };
}; int main()
{
std::cout<<Factorial<>::value;
std::cout<<Factorial<>::value;
}

-----------------------------------------------------

使用TMP一般可以有如下效果:

(1)确保量度单位正确。

(2)优化矩阵运算。

(3)生成客户定制的设计模式。

EC读书笔记系列之18:条款47、48的更多相关文章

  1. EC读书笔记系列之16:条款35、36、37、38、39、40

    条款35 考虑virtual函数以外的其他选择 记住: ★virtual函数的替代方案包括NVI手法及Strategy模式的多种形式.NVI手法自身是一个特殊形式的Template Method模式 ...

  2. EC读书笔记系列之10:条款16、17

    条款18 让接口容易被正确使用,不易被误用 记住: ★“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容 ★“阻止误用”的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的 ...

  3. EC读书笔记系列之1:条款1、条款2、条款3

    条款1:视C++为一个语言联邦 记住: ★C++高效编程守则视状况而变化,这取决于你使用C++的哪一部分 C: Object-oriented c++: Template c++: STL 条款2:尽 ...

  4. EC读书笔记系列之20:条款53、54、55

    条款53 不要轻忽编译器的警告 记住: ★严肃对待编译器发出的警告信息.努力在你的编译器的最高(最严苛)警告级别下争取“无任何警告”的荣誉 ★不要过度依赖编译器的报警能力,∵不同的编译器对待事情的态度 ...

  5. EC读书笔记系列之19:条款49、50、51、52

    条款49 了解new-handler的行为 记住: ★set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用 ★Nothrow new是一个颇为局限的工具,∵其只适用于内存 ...

  6. EC读书笔记系列之17:条款41、42、43、44、45、46

    条款41 了解隐式接口与编译器多态 记住: ★classes和templates都支持接口和多态 ★对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函 ...

  7. EC读书笔记系列之15:条款32、33、34

    条款32 确保你的public继承塑模出is-a关系 记住: ★public继承意味着is-a.适用于base class身上的每一件事情一定也适用于derived class身上,∵每一个deriv ...

  8. EC读书笔记系列之14:条款26、27、28、29、30、31

    条款26 尽可能延后变量定义式的出现时间(Lazy evaluation) 记住: ★尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率 ----------------------- ...

  9. EC读书笔记系列之12:条款22、23、24

    条款22 将成员变量声明为private 记住: ★切记将成员变量声明为private.这可赋予客户访问数据的一致性.可细微划分访问控制.允诺约束条件获得保证,并提供class作者以充分的实现弹性. ...

随机推荐

  1. 80端口被NT kernel & System 占用pid= 4的解决方法

    引用http://www.2cto.com/os/201111/111269.html的方法.亲测可用 该进程是Http.sys.它是http API的驱动组件,Http栈服务器.如果该端口被Http ...

  2. C++中如何split字符串(转)

    #include <iostream> #include <sstream> #include <string> using namespace std; int ...

  3. UIImageView~动画播放的内存优化

    我目前学到的知识,播放动画的步骤就是下面的几个步骤,把照片资源放到数组里面,通过动画animationImage加载数组,设置动画播放的 时间和次数完成播放. 后来通过看一些视频了解到:当需要播放多个 ...

  4. 如何查找ORACLE中的跟踪文件

    一.跟踪文件是干什么用的?        跟踪文件中包含了大量而详细的诊断和调试信息.通过对跟踪文件的解读和分析,我们可以定位问题.分析问题和解决问题.从跟踪文件的产生的来源来看,跟踪文件又可以分为两 ...

  5. SSO单点登录(转载)

    昨天和几位朋友探讨到了这个话题,发现虽然单点登录,或者叫做独立的passport登录虽然已经有了很多实现方法,但是能真正了解并实现的人却并不太多,所以些下此文,希望从原理到实现,能让大家了解的多一些 ...

  6. Ubuntu 14.04卸载安装失败的Mysql数据库,以及重新安装配置

    一.删除原来Mysql 1.删除mysql的数据文件 sudo rm /var/lib/mysql/ -R 2.删除mqsql的配置文件 sudo rm /etc/mysql/ -R 3.自动卸载my ...

  7. Nginx 配置指令的执行顺序(五)

    Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 响应的使命.正因为其重要性,这个阶段的配 ...

  8. Oracle EBS-SQL (PO-17):检查供货比例不为100%.sql

    select           * from           apps.MRP_SOURCING_RULES msrwhere           organization_id=X.    a ...

  9. 在类似qq或者微信聊天中。如何根据不同的手机发送图片

    原文:在类似qq或者微信聊天中.如何根据不同的手机发送图片   前一段时间,公司自己要求做多客服开发,但是对于发送图片这一块,当时很苦恼,我用自己的手机(米2)测试,不管是本地,还是云相册,最新照片. ...

  10. 获取ActiveX控件本身所在的路径 和 error PRJ0050

    一. CString   GetCurPath()     {     TCHAR       exeFullPath[MAX_PATH];     CString       strPath;    ...