前言:毕设时在开源库上做的程序,但是源码看得很晕(当时导师告诉我这是模板元编程,可以不用太在乎),最近自己造轮子时想学习STL的源码,但也是一样的感觉,大致了解他这么做要干什么,但是不知道里面的机制。于是开始学习《C++模板元编程》,看完第二章对一些东西豁然开朗。

PS:该书也有点老了,C++11标准还没出来,主要用的Boost库。

Traits(特征)

说正题,在STL中经常可以见到后缀为traits的名字,比如经常用到的std::string,本质是类模板basic_string的第一个参数charT为char的实例化。相应的,用来表示宽字节的字符串模板类wstring,charT为wchar_t。

template < class charT,
class traits = char_traits<charT>, // basic_string::traits_type
class Alloc = allocator<charT> // basic_string::allocator_type
> class basic_string;
typedef basic_string<char, char_traits<char>, allocator<char> >
string;

根据模板声明,3个模板参数,charT表示存放字符类型(Char Type);Alloc则是allocator<T>的实例化,通常的new是一次性分配完内存再在内存上构造T类型的元素,allocator<T>则分离了这两个过程,可以先分配一段连续的内存空间,相当于内存池,再直接在上面构造T类型元素,不需要使用时销毁元素即可,内存空间并未释放,可以重新在上面构造新的元素。这样就省去了每次都重新分配空间再构造元素的过程。有点类似对C的malloc/free的封装。(没仔细研究,不去祥述)

第2模板参数traits则是char_traits模板参数为charT的实例化

template<class _Elem>
struct char_traits
: public _Char_traits<_Elem, long>
{ // properties of a string or stream unknown element
};

可以发现char_traits继承于由_Char_traits的实例化的模板类,可以提供扩展。这里没有扩展,基本可以将char_traits等价模板类于_Char_traits<_Elem, long>

template<class _Elem,
class _Int_type>
struct _Char_traits
{ // properties of a string or stream element
typedef _Elem char_type;
typedef _Int_type int_type;
typedef streampos pos_type;
typedef streamoff off_type;
typedef _Mbstatet state_type;
// ...
}

省略了后面的静态方法,可以看出在此之前有5个typedef。在C++中,类的内部可以用typedef定义类型别名,可以像使用静态成员一样使用它。
也就是说可以用模板类_Char_traits<_Elem, _Int_type>::char_type来得到类型名。结合之前所说,char_traits继承自该模板类,因此可以用char_traits::char_type来得到类型名。可以发现,对模板类进行继承有点像对模板类进行实例化获取类型别名(比如typedef std::map<int, std::string> MapIntStr;),大大减轻了键盘输入的压力。

回过头来,std::basic_string的第2个模板参数traits是由字符类型charT作为模板参数(_Elem)实例化的char_traits

也就是说在std::basic_string中,通过模板参数traits可以直接获得类型。  比如traits::char_type等价于charT。

问题来了:为什么要特意给模板参数再套到一个模板上?

直接借用书上对迭代器traits的例子了,跟char_traits其实是类似的。

当我们需要对迭代器进行交换时(功能是交换迭代器指向的值)

    template <class FwIt1, class FwIt2>
void iter_swap(FwIt1 i1, FwIt2 i2)
{
typename FwIt1::value_type tmp = *i1;
*i1 = *i2;
*i2 = tmp;
}

这个实现有个问题,FwIt必须就像char_traits一样,内部有句typedef把某种类型取个别名叫value_type。如果是自己实现新的迭代器,可以一直按照这个标准在每个类模板里加上对应的typedef。

问题在于,且不谈其他的类模板是否实现了typedef,就指针而言在iter_swap中是不兼容的。因此需要加个像适配器一样的东西,让指针也能接入到iter_swap中。

现在我们编写一个iterator_traits作为适配器。

    template <class Iter>
struct iterator_traits
{
typedef typename Iter::value_type value_type;
};

类中只有一行代码进行类型别名定义,这样iterator_traits<Iter>::value_type就等价于Iter::value_type,用类型实例化某个类模板来取代类型自己。

C++模板有个优秀的特性是特例化(specialization),当模板参数有着某种特别类型时,选择实例化“特例类型”而不是通用模板。比如std::vector<bool>和其他一般的std::vector<T>的内部实现是不一样的。

对指针类型进行特例化后

    template <class T>
struct iterator_traits<T*>
{
typedef typename T value_type;
};

这样假如是用指针类型来实例化iterator_traits,将会选择这个来实例化,而不是前一个通用形式。(选择哪一个模板实例化这里也不详述,《C++ Primer》有相关说明)
原来的iter_swap就变成了

    template <class FwIt1, class FwIt2>
void iter_swap(FwIt1 i1, FwIt2 i2)
{
// typename FwIt1::value_type tmp = *i1;
typename
iterator_traits<FwIt1>::value_type
tmp = *i1;
*i1 = *i2;
*i2 = tmp;
}

可以看出,在模板参数之上再套个模板是起到适配器的作用,在类模板中需要用更通用的方式取得类型信息,相当于在一个体系内达成统一的标准。

C++标准库已经实现了iterator_traits,只有迭代器才能用作模板参数,因为只有迭代器类实现了那几个特定的typedef。另外,iterator_traits也针对指针类型进行了特例化。假如用容器作为模板参数,会报错(比如没有实现哪个typedef)。

回到最初的问题:什么是模板元编程?使用这种traits技巧跟模板元编程有什么关系?

简要地解释元编程(Meta Programming),元编程类似一个解释器,比如C/C++都是生成了汇编最终生成机器码让计算机运行的。

而模板元编程(TMP)是在C++的体系内,模板实例化后会变成新的代码,但这份新的代码仍然是C++代码,模板的优点就像它的名字一样,套个不同的参数能够刻画出一组组功能相似类型又不同的类或函数。比如std::vector<int>和std::vector<float>就是两种类型,但是却有着通用的方法。

模板的实例化是发生在编译期间的,这是C++ TMP的根本原因,能够在编译期间完成的事情不需要在运行期间做到,虽然编译时间更长,但是会得到运行效率的提升。

直接给出《C++模板元编程》第一章的例子,使用模板元编程来把二进制数转换成十进制数,在编译好时已经计算出了实例化的值。

template <unsigned long N>
struct binary
{
static unsigned const value
= binary<N / 10>::value << 1
| N % 10;
}; // 特化
template <>
struct binary<0>
{
static unsigned const value = 0;
};

它的思想是,一段短的类模板代码等价于好长好长的一个常量表达式(编译期间就确定),在运行时使用binary<110010>::value相当于直接使用一个常量表达式(用作赋值、实参等等)。而若按照传统的方法写个函数来计算,就是在运行时进行计算。

而类型跟值本质上是一样的,就像我们在类中进行了typedef,在类外部使用别名的方式就跟使用静态成员方式一样。模板元编程不仅仅是做这种数值运算,也可以做类型运算,前面花了较大大篇幅提到traits就是一个典例。

初识C++模板元编程(Template Mega Programming)的更多相关文章

  1. 初识c++模板元编程

    模板元编程(Template metaprogramming,简称TMP)是编译器内执行的程序,编译器读入template,编译输出的结果再与其他源码一起经过普通编译过程生成目标文件.通俗来说,普通运 ...

  2. C++模板元编程(C++ template metaprogramming)

    实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版) 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得 ...

  3. 模板元编程(Template metaprogramming)

    https://en.wikipedia.org/wiki/Template_metaprogramming 没看懂...只知道了模板元编程的代码是在编译期运行的... 敲了2个例子: 1. #inc ...

  4. effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  5. 读书笔记 effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  6. C++ 模板元编程 学习笔记

    https://blog.csdn.net/K346K346/article/details/82748163 https://www.jianshu.com/p/b56d59f77d53 https ...

  7. C++模板元编程 - 函数重载决议选择工具(不知道起什么好名)完成

    这个还是基于之前实现的那个MultiState,为了实现三种类型“大类”的函数重载决议:所有整数.所有浮点数.字符串,分别将这三种“大类”的数据分配到对应的Converter上. 为此实现了一些方便的 ...

  8. C++模板元编程 - 挖新坑的时候探索到了模板元编程的新玩法

    C++真是一门自由的语言,虽然糖没有C#那么多,但是你想要怎么写,想要实现什么,想要用某种编程范式或者语言特性,它都会提供. 开大数运算类的新坑的时候(又是坑),无意中需要解决一个需求:大数类需要分别 ...

  9. 读书笔记_Effective_C++_条款四十八:了解模板元编程

    作为模板部分的结束节,本条款谈到了模板元编程,元编程本质上就是将运行期的代价转移到编译期,它利用template编译生成C++源码,举下面阶乘例子: template <int N> st ...

随机推荐

  1. Git 从了解到放弃

    1. 简单介绍 1.1. git起源 在1991年linus创建了Linux从此linux成为服务器领域的佼佼者,大部分web服务器.邮件.数据库各种服务器端程序都安装在了linux上面运行,主要是因 ...

  2. 【转】OpenWRT开发自定义应用方法

    [转]OpenWRT开发自定义应用方法 转自:http://blog.csdn.net/rudyn/article/details/38616783 OpenWRT编译成功完成后,所有的产品都会放在编 ...

  3. 【http】HTTP请求方法 之 OPTIONS

    OPTIONS方法是用于请求获得由Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项.通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务 ...

  4. 【css】响应式布局入门【转】

    最近研究响应式设计框架的时候,发现网上很多相关的属性介绍,却很少有系统的入门级使用的文章,我自己整理了一篇入门知识,并没有什么高深的理论,也不牵扯到框架. 目前已经越来越多的站点以及wap站点使用响应 ...

  5. ubuntu安装搜狗输入法的相关问题

    最近换了一次电脑,要重新装系统,就选择了ubuntu14.04 LTS版本的,这个版本官方支持到2019年了,所以相对来说比较稳定,也不用担心软件源的问题.不像14.10那样,现在官方已经没有软件源的 ...

  6. xss 防御

    系列 防御原则 第一.在输入方面对所有用户提交内容进行可靠的输入验证,提交内容包括URL.查询关键字.http头.post数据等 第二.在输出方面,在用户输内容中使用 <XMP>标签 还是 ...

  7. iOS 阶段学习第三天笔记(运算符)

    iOS学习(C语言)知识点整理笔记 1.运算符 一.算术运算符 1)表达式由变量.常量.运算符构成,有确定的类型和值 2)算术运算符包括: +(加),-(减),*(乘),/(除),%(模) 3)算术运 ...

  8. SpringMVC中的适配器

    适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作 适用场景: 1.已经存在的类的接口不符合我们的需 ...

  9. js的事件循环机制和任务队列

    上篇讲异步的时候,提到了同步队列和异步队列的说法,其实只是一种形象的称呼,分别代表主线程中的任务和任务队列中的任务,那么此篇我们就来详细探讨这两者. 一.来张图感受一下 如果看完觉得一脸懵逼,请继续往 ...

  10. python常用模块之shelve模块

    python常用模块之shelve模块 shelve模块是一个简单的k,v将内存中的数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据类型 我们在上面讲json.pickle ...