STL源码剖析--侯捷

  • 总结

  尽管现在的很多语言支持参数类型的判别,但是c/c++并不支持这一特性。

  但是我们可以通过一些技巧使得c++具有自动判别参数类型的特性。

  • 模板

  我们都知道在模板类和模板函数中我们不用具体指定参数的型别,编译器会自动的判别参数的类型。

  所以我们想可不可以把编译器运行时所确定的型别萃取出来呢?

  可以通过内嵌型别实现。

#include <iostream>
using namespace std; template <class T>
struct MyIter{
typedef T value_type;
//声明内嵌型别为value_type
T* ptr;
MyIter(T* p = ):ptr(p){ }
T& operator*()const{ return *ptr; }
}; template <class I>
typename I::value_type
//上面这一行告诉编译器这个型别,不然func无法返回
func(I ite)
{ return *ite; } int main(int argc, const char *argv[])
{
MyIter<int> ite(new int());
cout << func(ite) << endl;
//
return ;
}
~

  这里的func函数就可以成功的把value_type萃取出来。

  但是这里还有一个隐蔽的陷阱。

  就是当我们这个迭代器是一个原生的指针时就会有问题,因为原生的指针并没有内嵌型别。

  这就需要这个模板针对这个类型写一个偏特化的版本。

 #include <iostream>
using namespace std; template <class T>
struct MyIter{
typedef T value_type;
//声明内嵌型别为value_type
T* ptr;
MyIter(T* p = ):ptr(p){ }
T& operator*()const{ return *ptr; }
}; template <class I>
struct iterator_traits_1{
typedef typename I::value_type value_type;
};
//对MyIter进行封装,获取它的内嵌型别 template <class T>
struct iterator_traits_1<T*>{
typedef T value_type;
};
//对原生指针进行型别的定义 template <class I>
typename iterator_traits_1<I>::value_type
//告诉编译器下面函数返回的型别
func(I ite)
{ return *ite; } int main(int argc, const char *argv[])
{
MyIter<int> ite(new int());
cout << func(ite) << endl;
//返回8
int a = ;
int *b = &a;
cout << func(b) << endl;
//返回5
return ;
}

  这里我们对原来的类型MyIter又多了一层封装,这样的好处呢就是可以让下面的函数对原生指针也可以使用。

  为了可以使用原生指针,我们又写了一个偏特化的版本。

  特化版本和偏特化的版本的区别是,特化版本只是针对某一特定的类型实现。

  而偏特化版本是对于一组特定的类型特化,更具一般性。

  这里我们是针对所有的原生指针生成一个偏特化版本。

  • 关于traits

  traits在这里的作用是非常重要的,它所扮演的角色不仅仅是对各个类型的兼容,而且也是一个“类型萃取机”。

  需要说明的一点就是要想兼容traits,必须有相关的内嵌型别定义;这一点在STL的迭代器中至关重要。

  • iterator部分源码重列

  

 //STL全部迭代器类型
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 {}; template <class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T&>
struct iterator{
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
}; //类型萃取机traits
template <class Iterator>
struct iterator_traits{
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type defference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
}; //针对原生指针的偏特化版本
template <class T>
struct iterator_traits<T*>{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
}; //这个函数可以很方便的决定某个迭代器的类型(category)
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&)
{
typedef typename iterator_traits<Iterator>::iterator_category category;
return category();
} //这个函数可以很方便的决定某个迭代器的distance_type
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&)
{
return static_cast<typename iterator_traits<Iterator>::difference_type*>();
} //这个函数可以很方便的决定某个迭代器的value_type
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&)
{
return static_cast<typename iterator_traits<Iterator>::value_type*>();
} //以下是整组的distance函数
//两个__distance函数中的第三个参数没有实际意义,仅仅是为了判别迭
//代器类型
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag){
iterator_traits<InputIterator>::difference_type n = ;
while(frist != last){
++first; ++n;
}
return n;
}
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIteratorIterator last,
random_access_iterator_tag){
return last - first;
} template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last)
{
typedef typename
iterator_traits<InputIterator>::iterator_category category;
return __distance(first, last, category());
}

  关于这些特性,在STL大量的使用,一定程度上补充了c++的不足;

  最重要的是这些特性帮助我们在程序编译时就完成了类型的判断,而不是自己写个函数在运行时判断,这样做

  更快,更高效。

c++:参数型别的推导的更多相关文章

  1. Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype

    条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...

  2. Effective Modern C++ 条款4:掌握查看型别推导结果的方法

    采用何种工具来查看型别推导结果,取决于你在软件开发过程的哪个阶段需要该信息.主要研究三个可能的阶段:撰写代码阶段.编译阶段.运行时阶段. IDE编译器 IDE中的代码编译器通常会在你将鼠标指针选停止某 ...

  3. Effective Modern C++ 条款2:理解auto型别推导

    在条款1中,我们已经了解了有关模板型别的推导的一切必要知识,那么也就意味着基本上了解了auto型别推导的一切必要知识. 因为,除了一个奇妙的例外情况,auto型别推导就是模板型别推导.尽管和模板型别推 ...

  4. Effective Modern C++  条款1:理解模板型别推导

    成百上千的程序员都在向函数模板传递实参,并拿到了完全满意的结果,而这些程序员中却有很多对这些函数使用的型别是如何被推导出的过程连最模糊的描述都讲不出来. 但是当模板型别推导规则应用于auto语境时,它 ...

  5. 图说函数模板右值引用参数(T&&)类型推导规则(C++11)

    见下图: 规律总结: 只要我们传递一个基本类型是A④的左值,那么,传递后,T的类型就是A&,形参在函数体中的类型就是A&. 只要我们传递一个基本类型是A的右值,那么,传递后,T的类型就 ...

  6. Java Comparator的范型类型推导问题

    问题 在项目中,有一处地方需要对日期区间进行排序 我需要以日期区间的开始日为第一优先级,结束日为第二优先级进行排序 代码 我当时写的代码如下: List<Pair<LocalDate, L ...

  7. STL——迭代器与traits编程技法

    一.迭代器 1. 迭代器设计思维——STL关键所在 在<Design Patterns>一书中对iterator模式定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素 ...

  8. C++17尝鲜:类模板中的模板参数自动推导

    模板参数自动推导 在C++17之前,类模板构造器的模板参数是不能像函数模板的模板参数那样被自动推导的,比如我们无法写 std::pair a{1, "a"s}; // C++17 ...

  9. Scalaz(27)- Inference & Unapply :类型的推导和匹配

    经过一段时间的摸索,用scala进行函数式编程的过程对我来说就好像是想着法儿如何将函数的款式对齐以及如何正确地匹配类型,真正是一种全新的体验,但好像有点太偏重学术型了. 本来不想花什么功夫在scala ...

随机推荐

  1. java base64编码 加密和解密(切记注意乱码问题)

    BASE64 编码是一种常用的字符编码,在很多地方都会用到.JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和 ...

  2. bzoj4443 SCOI2015 小凸玩矩阵 matrix

    传送门:bzoj4443 题解 很水的一道网络流,显然可以二分答案,然后我们希望第\(k\)大尽量小,那么对于一个\(mid\),我们应尽量选择更小的,然后跑二分图最大匹配来验证. code

  3. 几个 PHP 的“魔术常量”

    PHP 向它运行的任何脚本提供了大量的预定义常量.不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了. 有八个魔术常量它们的值随着它们在 ...

  4. POJ 3094 Quicksum(简单的问题)

    [简要题意]:题意是非常easy. 看样能理解 [分析]:略. 读取字符串. // 200K 0Ms #include<iostream> using namespace std; int ...

  5. 理解ArcGIS Javascript Viewer Widget及编程模型

    一个ArcGIS Javascript Viewer for JavaScript Widget是一组可以共享.迁移及部署到JavaScript View程序中的的文本文件.通常,一个程序员如果要开发 ...

  6. [转] Bound Service的三种方式(Binder、 Messenger、 AIDL)

    首先要明白需要的情景,然后对三种方式进行选择: (一)可以接收Service的信息(获取Service中的方法),但不可以给Service发送信息 (二) 使用Messenger既可以接受Servic ...

  7. CTE在Oracle和Sqlserver中使用的差异

    CTE是一个很好用的工具,他可以帮助我们清晰代码结构,减少临时表使用,同时oracle和sqlserver都提供支持.但在oracle和sqlserver中使用CTE也存在一定区别. Oracle使用 ...

  8. 可以直接拿来用的15个jQuery代码片段

    jQuery里提供了许多创建交互式网站的方法,在开发Web项目时,开发人员应该好好利用jQuery代码,它们不仅能给网站带来各种动画.特效,还会提高网站的用户体验. 本文收集了15段非常实用的jQue ...

  9. sql知识点的积累和使用过的例子

    越来越发现自己的sql方面的知识的欠缺,所以只能放低姿态一点一点的学了 一 游标和charIndex的使用. 游标我一直没用过,以前只是在同事们写的存储过程里见过,但是一直没看明白(可是我就是比较笨吧 ...

  10. win7 下配置 java 环境变量

    首先,你应该已经安装了 java 的 JDK 了,笔者安装的是:jdk-7u7-windows-x64 接下来主要讲怎么配置 java 的环境变量,也是为了以后哪天自己忘记了做个备份 1.进入“计算机 ...