C++ traits学习笔记(一)
我的C++ traits学习笔记,这是第一篇。
刚刚接触traits技术,很多地方不知道理解的对还是不对,有路过的朋友如果发现问题请不吝指教,谢了!
翻译文章怪不容易的,转载请注明出处http://www.cnblogs.com/youthlion/archive/2011/12/01/2255618.html,再次感谢!
资料(a)
http://en.wikipedia.org/wiki/Traits_class
a traits class is a class template used to associate information or behaviour to a compile-time entity, typically a data type or a constant, without modifying the existing entity. In the C++ programming language, this is normally achieved by defining a primary class template, and creating explicit or partial specializations for the relevant types.
traits class是个类模板,在不修改一个实体(通常是数据类型或常量)的前提下,把属性和方法关联到一个编译时的实体。在c++中的具体实现方式是:首先定义一个类模板,然后进行显式特化或进行相关类型的部分特化。
我的理解是:traits是服务于泛型编程的,其目的是让模板更加通用,同时把一些细节向普通的模板用户隐藏起来。当用不同的类型去实例化一个模板时,不可避免有些类型会存在一些与众不同的属性,若考虑这些特性的话,可能会导致形成的模板不够“泛型”或是过于繁琐,而traits的作用是把这些特殊属性隐藏起来,从而实现让模板更加通用。
资料(b)
traits class技术最初似乎是Nathan C. Myers提出来的,这是他在1995年关于traits class的文章。草草地翻译一下:
问题描述:
ANSI/ISO C++标准库工作组那伙大牛在搞标准库对国际化的支持的时候遇到了问题。
比如,在iostream这个库里,steambuf的接口依赖于EOF,而EOF与所有字符类型都不同,在传统的库里,EOF为int,读取字符的那些函数的返回值也是整型:
1: class streambuf {
2: ...
3: int sgetc(); // return the next character, or EOF.
4: int sgetn(char*, int N); // get N characters.
5: };
如果把streambuf参数化为某种字符类型会咋样?此时我们不但需要一种针对字符的类型(type),还需要针对EOF的类型(type)。好吧,搞成这样好了:
1: template <class charT, class intT>
2: class basic_streambuf {
3: ...
4: intT sgetc();
5: int sgetn(charT*, int N);
6: };
iostream的用户们可不关心文件结束标志是个啥……更令人纠结的是当sgetc遇到文件末尾时该返回什么值?一定要有那个intT模板参数吗?那个额外的模板参数让你蛋疼了没?
Traits技术:
大牛们淡定地说,既然出了问题,索性咱就搞个新技术出来吧。。。这回咱不往咱原始的模板里添加乱七八糟的参数,咱干脆再定义一个模板。这个模板压根儿没打算让用户直接用,所以名字也可以稍微长一点,嗯。。也许400个单词的名字够说清这个模板的作用了。
1: template <class charT>
2: struct ios_char_traits { };
默认的traits class模板是个空模板。现在,对于真正的字符类型,咱就可以把这个模板特化一下,再加上一些有用的语义。
1: struct ios_char_traits<char> {
2: typedef char char_type;
3: typedef int int_type;
4: static inline int_type eof() { return EOF; }
5: };
哦,那位蹲着的同学发现了。。现在这个模板里面没有数据成员,而且只提供了public的定义(注意是struct而非class,默认访问权限,你懂的)。现在就开始重新定义streambuf模板:
1: template <class charT>
2: class basic_streambuf {
3: public:
4: typedef ios_char_traits<charT> traits_type;
5: typedef traits_type::int_type int_type;
6: int_type eof() { return traits_type::eof(); }
7: ...
8: int_type sgetc();
9: int sgetn(charT*, int N);
10: };
现在除了那些刺眼的typedef,这个模板和原来的也没啥太大区别,但是确实只剩一个用户感兴趣的模板参数了。编译器会去字符的traits class里查找关于字符类型的信息。
除了一些变量的声明方式有些不同,使用这个模板的用户代码也会和从前一样。
要在stream上使用一种新的字符类型,只要把ios_char_traits特化成这种新类型就可以了。现在小试一下,为宽字符提供支持:
1: struct ios_char_traits<wchar_t> {
2: typedef wchar_t char_type;
3: typedef wint_t int_type;
4: static inline int_type eof() { return WEOF; }
5: };
现在basic_streambuf模板和两个traits类的关系如下图:
字符串可以以同样的方式实现。
啥时候可以用traits class?目前为止现在我还没有什么心得,请大家自己体会作者原话:This technique turns out to be useful anywhere that a template must be applied to native types, or to any type for which you cannot add members as required for the template's operations.
另一个例子
下面咱来看看这种技术还可以用在什么地方。这个例子来自ANSI/ISO C++ [Draft] Standard(非最终版本)
假设正要写一个数值分析库,这个库处理float,double,long double这些数值类型。每种类型具有最大指数值和尾数所占的空间,还有“epsilon”函数(精度控制,如DBL_EPSILON)等。标准头文件<float.h>中定义了这些参数,但是一个参数化为数值类型的模板咋知道最大指数值是FLT_MAX_EXP还是DBL_MAX_EXP呢?
看看特化的traits模板如何搞定这个问题:
1: template <class numT>
2: struct float_traits { };
3:
4: struct float_traits<float> {
5: typedef float float_type;
6: enum { max_exponent = FLT_MAX_EXP };
7: static inline float_type epsilon() { return FLT_EPSILON; }
8: ...
9: };
10:
11: struct float_traits<double> {
12: typedef double float_type;
13: enum { max_exponent = DBL_MAX_EXP };
14: static inline float_type epsilon() { return DBL_EPSILON; }
15: ...
16: };
现在不管啥类型,模板的使用者都可以无差别使用max_exponent了。比如这儿有个矩阵类模板:
1: template <class numT>
2: class matrix {
3: public:
4: typedef numT num_type;
5: typedef float_traits<num_type> traits_type;
6: inline num_type epsilon() { return traits_type::epsilon(); }
7: ...
8: };
当我用double来实例化这个矩阵模板时,会有
1: matrix<double> somemat;
此时模板会被实例化为
1: class matrix {
2: public:
3: typedef double num_type;
4: typedef float_traits<num_type> traits_type; //此处num_type实际是double了
5: //这样又实例化了一个float_traits<double>,此时max_exponent为DBL_MAX_EXP,
6: //epsilon神马的也都是float_traits<double>所对应的
嗯,蹲着的那位同学又发现了。。现在所有的例子里面,每个模板都以public的方式提供了其参数对应的typedef,以及依赖这些typedef的常量(MAX_XXXX),返回值类型啥的(float_type)。没错:必须提供了相关类型的typedef,才能用这种类型去实例化模板。
关于默认模板参数
这个例子来自Stroustrup的《Design and Evolution of C++》359页,首先假设有个有点儿像traits的模板CMP:
1: template <class T> class CMP {
2: static bool eq(T a, T b) { return a == b; }
3: static bool lt(T a, T b) { return a < b; }
4: };
还有一个普通的字符串模板:
1: template <class charT> class basic_string;
现在可以定义一个对字符串进行比较的compare()函数:
1: template <class charT, class C = CMP<charT>>
2: int compare(const basic_string<charT>&,const basic_string<charT>&);
好,老少爷们儿们,请注意那个compare<>()的参数:首先,第二个参数C默认不是一个类,而是个实例化的类模板;其次,这个实例化的CMP模板的参数又是外层模板的第一个参数。OK,咱再绕回开始那个streambuf模板,看看出了些啥事儿:
1: template <class charT, class traits = ios_char_traits<charT> >
2: class basic_streambuf {
3: public:
4: typedef traits traits_type;
5: typedef traits_type::int_type int_type;
6: int_type eof() { return traits_type::eof(); }
7: ...
8: int_type sgetc();
9: int sgetn(charT*, int N);
10: };
这样一来,虽然模板又变成两个参数,但是通常情况下,模板的用户不需要关心第二个参数,有特殊需要时,又可以用特殊类型去特化那个traits。
运行时可变(runtime-variable)的traits
还可以进一步泛化。咱还没见着basic_streambuf的构造函数是吧?
1: template <class charT, class traits = ios_char_traits<charT> >
2: class basic_streambuf {
3: traits traits_; // member data
4: ...
5: public:
6: basic_streambuf(const traits& b = traits())
7: : traits_(b) { ... }
8:
9: int_type eof() { return traits_.eof(); }
10: };
通过添加构造函数的默认参数,我们在编译时和运行时都可以使用traits模板参数了。
转载于:https://www.cnblogs.com/youthlion/archive/2011/12/01/2255618.html
C++ traits学习笔记(一)相关推荐
- C++STL学习笔记(4) 分配器(Allocator)
在前面的博客<C++ STL学习笔记(3) 分配器Allocator,OOP, GP简单介绍>中,简单的介绍了分配器再STL的容器中所担当的角色,这一节对STL六大部件之一的分配器进行详细 ...
- 《C++标准库》学习笔记 — STL —流
<C++标准库>学习笔记 - STL -流 一.操控器 1.原理 2.自定义操控器 3.控制输入的宽度 二.自定义 I/O 操作符 1.重载输出操作符 2.输入操作符 三.自定义格式化标志 ...
- 【从零开始的大数据学习】Flink官方教程学习笔记(一)
Flink官方教程学习笔记 学习资源 基础Scala语法 Scala数据结构专题 声明变量 代码块 函数(function) 方法(methods) Traits (接口) class(类) tupl ...
- Effective C++ 学习笔记 第七章:模板与泛型编程
第一章见 Effective C++ 学习笔记 第一章:让自己习惯 C++ 第二章见 Effective C++ 学习笔记 第二章:构造.析构.赋值运算 第三章见 Effective C++ 学习笔记 ...
- Pointproofs 学习笔记3——代码解析
1. 引言 之前的系列博客有: 1)Pointproofs: Aggregating Proofs for Multiple Vector Commitments 学习笔记1中,主要对 Algoran ...
- 《TP5.0学习笔记---配置篇》
TP5.0学习笔记 TP5目录结构介绍 application目录是应用目录,我们整个应用所有的内容都写在这个目录中,在后续开发中,我们更多的时候都是在编写这个目录中的文件.在它里边有一个index文 ...
- chipyard学习笔记
目录 一 . 进入chipyard后的环境变量设置 3 二 . 6. Customization 自定义SoC学习: 3 Chipyard document 6.1 : 3 Chipyard docu ...
- 中国大学MOOCPython语言程序设计(北京理工大学)第7-9周学习笔记和课后练习
中国大学MOOCPython语言程序设计(北京理工大学)第7-9周学习笔记和课后练习 第7周 文件和数据格式化 7.1 文件的使用 7.2 实例11: 自动轨迹绘制 7.3 一维数据的格式化和处理 7 ...
- DMU软件 语法高亮 vim设置--学习笔记6
用vim编程时, DMU的关键词没有语法高亮, 看着不舒服, 就进行一下设置, 并记录过程. 设置的效果如下 设置流程 本次设置的比较简单, 将关键词分为: 模型model, 比如DMU1, DMU2 ...
- 读《周志明的软件架构课》--学习笔记和感想随笔
主要记录对周老师的<周志明的软件架构课>的学习笔记,本篇章是对课程不可变基础设施部分的学习,从虚拟化的技术理解容器技术的产生和发展,从部署运维角度理解应用的封装,从网络虚拟化理解容器网络, ...
最新文章
- mysqldump 导出数据库各参数详细说明
- 一篇不错的讲DHCP的文章
- Java必刷100题
- unity的vr场景怎么做_如何用Unity快速创建一个VR体验
- 自下而上滚动公告栏(可悬停)
- Pandas数据分析常用数据操作(3年总结)
- Mysql事项,视图,函数,触发器命令
- delphi 到出execl2010 文件损坏_如何修复Linux中损坏的软件包?
- python socket客户端_python 使用socket模拟tcp客户端和tcp服务器端
- 第15届创新英语大赛初赛第二阶段题目
- 21秋期末考试成本会计10168k2
- HTML5新增的视频元素与音频元素
- Linux Shell脚本面试25问
- uniapp css实现轮播图片逐渐放大效果
- 28句最精辟有哲理的生活感悟说说,经典至极,总有一句说到你的心里
- 浏览器功能大比拼:谁更出彩?
- 李沐《动手学》-kaggle-房价预测-submission
- 使用swiper插件时不会自动轮播
- Linux rpm 文件安装
- 对软件架构设计的一些总结和理解