`

C++类型萃取技术

    博客分类:
  • c++
阅读更多

Traits技术可以用来获得一个 类型 的相关信息的。

  首先假如有以下一个泛型的迭代器类,其中类型参数 T 为迭代器所指向的类型:

 

    template <typename T>

    class myIterator

    {

     ...

    };

 

  当我们使用myIterator时,怎样才能获知它所指向的元素的类型呢?我们可以为这个类加入一个内嵌类型,像这样:

    template <typename T>

    class myIterator

    {

            typedef  T value_type; 

      ...

    };

  这样当我们使用myIterator类型时,可以通过 myIterator::value_type来获得相应的myIterator所指向的类型。

 

  现在我们来设计一个算法,使用这个信息。

    template <typename T>

    typename myIterator<T>::value_type Foo(myIterator<T> i)

    {

     ...

    }

  这里我们定义了一个函数Foo,它的返回为为  参数i 所指向的类型,也就是T,那么我们为什么还要兴师动众的使用那个value_type呢? 那是因为,当我们希望修改Foo函数,使它能够适应所有类型的迭代器时,我们可以这样写:

    template <typename I> //这里的I可以是任意类型的迭代器

    typename I::value_type Foo(I i)

    {

     ...

    }

   现在,任意定义了 value_type内嵌类型的迭代器都可以做为Foo的参数了,并且Foo的返回值的类型将与相应迭代器所指的元素的类型一致。至此一切问题似乎都已解决,我们并没有使用任何特殊的技术。然而当考虑到以下情况时,新的问题便显现出来了:

 

  原生指针也完全可以做为迭代器来使用,然而我们显然没有办法为原生指针添加一个value_type的内嵌类型,如此一来我们的Foo()函数就不能适用原生指针了,这不能不说是一大缺憾。那么有什么办法可以解决这个问题呢? 此时便是我们的主角:类型信息萃取技术Traits 登场的时候了

 

 

  我们可以不直接使用myIterator的value_type,而是通过另一个类来把这个信息提取出来:

    template <typename T>

    class Traits

    {

              typedef typename T::value_type value_type;

    };

  这样,我们可以通过 Traits<myIterator>::value_type 来获得myIterator的value_type,于是我们把Foo函数改写成:

    template <typename I> //这里的I可以是任意类型的迭代器

    typename Traits<I>::value_type Foo(I i)

    {

     ...

    }

  然而,即使这样,那个原生指针的问题仍然没有解决,因为Trait类一样没办法获得原生指针的相关信息。于是我们祭出C++的又一件利器--偏特化(partial specialization):

    template <typename T>

    class Traits<T*> //注意 这里针对原生指针进行了偏特化

    {

          typedef typename T value_type;

    };

  通过上面这个 Traits的偏特化版本,我们陈述了这样一个事实:一个 T* 类型的指针所指向的元素的类型为 T。

 

  如此一来,我们的 Foo函数就完全可以适用于原生指针了。比如:

    int * p;

    ....

    int i = Foo(p);

 Traits会自动推导出 p 所指元素的类型为 int,从而Foo正确返回。

 

 

 

 

自从C++中引入了template后,以泛型技术为中心的设计得到了长足的进步。STL就是这个阶段杰出的产物。STL的目标就是要把数据和算法分开,分别对其进行设计,之后通过一种名为iterator的东西,把这二者再粘接到一起。设计模式中,关于iterator的描述为:一种能够顺序访问容器中每个元素的方法,使用该方法不能暴露容器内部的表达方式。可以说,类型萃取技术就是为了要解决和iterator有关的问题的,下面,我们就来看看整个故事。

应该说,迭代器就是一种智能指针,因此,它也就拥有了一般指针的所有特点——能够对其进行*和->操作。但是在遍历容器的时候,不可避免的要对遍历的容器内部有所了解,所以,设计一个迭代器也就自然而然的变成了数据结构开发者的一个义务,而这些iterators的表现都是一样的,这种内外的差异,对用户来说,是完全透明的,

第一部分 为什么要有萃取技术

既然是一种智能指针,iterator也要对一个原生指针进行封装,而问题就源于此,当我们需要这个原生指针所指对象的类型的时候(例如声明变量),怎么办呢?

Case1 对于函数的局部变量

这种情况我们可以采用模版的参数推导,例如:

template <class T> void func(T iter)

如果T是某个指向特定对象的指针,那么在func中需要指针所指向对象类型的变量的时候,怎么办呢?这个还比较容易,模板的参数推导机制可以完成任务,如下:

template <class T, class U>

void func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

void func(T t) {

    func_impl(t, *t); // forward the task to func_impl

}

通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型,但是事情往往不那么简单。例如,如果我想把传递给func的这个指针参数所指的类型作为返回值,显然这个方法不能凑效了,这就是我们的case 2。

Case2 对于函数的返回值

尽管在func_impl中我们可以把U作为函数的返回值,但是问题是用户需要调用的是func,于是,你不可能写出下面的代码:

template <class T, class U>

U func_impl(T t, U u) {

    U temp; // OK, we’ve got the type

            // The rest work of func…

}

template <class T>

(*T) func(T t) { // !!!Wrong code

    return func_impl(t, *t); // forward the task to func_impl

}

int i =10;

cout<<func(&i)<<endl; // !!! Can’t pass compile

红色的部分概念上如此正确,不过所有的编译器都会让你失望。这个问题解决起来也不难,只要做一个iterator,然后在定义的时候为其指向的对象类型制定一个别名,就好了,像下面这样:

template <class T>

struct MyIter {

    typedef T value_type; // A nested type declaration, important!!!

    T* ptr;

    MyIter(T* p = 0) : ptr(p) {}

    T& operator*() const { return *ptr; }

};

而后只要需要其指向的对象的类型,只要直接引用就好了,例如:

template <class I>

typename I::value_type func(I iter) { return *iter; }

很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为func如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:

int *p = new int(52);

cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here

我们的func无法支持原生指针,这显然是不能接受的。此时,template partial specialization就派上了用场。

Solution :template partial specialization是救世主

既然刚才的设计方案仍不完美,那我们就再加一个间接层,把智能指针和原生指针统统的封装起来。在讨论之前,先要澄清一下template partial specialization的含义。所谓的partial specialization和模板的默认参数是完全不同的两件事情,前者指的是当参数为某一类特定类型的时候,采用特殊的设计,也就是说是“针对template参数更进一步的条件限制所设计出来的一个特化版本”;而默认参数则是当不提供某些参数的时候,使用的一个缺省。

参考:partial specialization的语法

Template <typename T> class C<T*> {} // 为所有类型为T*的参数而准备的特殊版本

好了,下面我们就找一个专职的负责人,用来封装迭代器封装的对象类型。首先,把我们刚才的MyIter重新包装一下:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

现在,我们的func又有了新的面貌:

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

尽管这次我们的函数返回值的长度有些吓人,但是,我们的确为原生指针找到了好的解决方案。只要为原生指针提供一个偏特化的iterator_traits就OK了。如下:

template <class I>

struct iterator_traits<T*> {

    typedef T value_type;

};

下面,我们终于可以让func同时支持智能指针和原生指针了:

template <class I>

struct iterator_traits {

    Typedef I::value_type value_type;

}

template <class T>

struct iterator_traits<T*> {

    typedef T value_type;

};

template <class I>

typename iterator_traits<I>::value_type func(I ite) {

    return *ite;

}

int main() {

    MyIter<int> iter = new int(520);

    int *p = new int(520);

    // This time the following two statements will success

    cout<<func(iter)<<endl;

    cout<<func(p)<<endl;

    return 0;

}

但是,我们离最后的成功还有最后一步,如果,我们需要声明一个value_type类型的左值,但是却给iterator_traits传递了一个const int*,显然结果有问题,于是,为const T*也另起炉灶,准备一份小炒:

template<class T>

struct iterator_traits<const T*> {

    typedef T value_type;

}

OK ,现在万事大吉,无论是正宗迭代器,原生指针,const原生指针,我们都可以利用iterator_traits萃取出其封装的对象的类型,萃取技术由此而来。

分享到:
评论

相关推荐

    Visual C++串口通信技术详解.(机械工业.李景峰.杨丽娜.潘恒)

    书名:《Visual C++串口通信技术详解》(机械工业出版社.李景峰.杨丽娜.潘恒) PDF格式扫描版,全书分为16章,共368页。2010年6月出版。 内容简介 本书介绍如何利用Visual C++集成开发环境进行串口通信程序开发。书...

    C++编程思想 例子和叙述讲解C++编程

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想 编程

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想.pdf

    《C++编程思想》,原名《Thinking in C++》,作者:Bruce Eckel,翻译:刘宗田等,出版社:机械工业出版社,ISBN:7111071166,pdf 格式,大小 4MB。本资源仅包括前七章的内容。 内容简介: 本书作者根据自己学习C++...

    用c++编写的某公司工资管理系统

    Saleman类派生的属性有当月销售额和酬金提取百分比,业绩工资为两者之积。也包括同名的pay函数,工资总额为基本工资加业绩工资。 Manager类派生的属性有固定奖金额和业绩系数,业绩工资为两者之积。工资总额也为基本...

    用c++编写网络流量统计系统

    用c++编写一个流量统计系统 为了方便网络编程,90年代初,由Microsoft联合了其他几家公司共同制定了一套WINDOWS下的网络编程接口,即Windows Sockets规范,它不是一种网络协议,而是一套开放的、支持多种协议的...

    C++ 语言 详细教程电子版

    本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流...

    c++ 编程思想 (高清pdf)

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    Thinking in C++高清版

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++ 编程思想 打印版 pdf

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想 PDF版 电子书

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想 打印版 pdf

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想(用简单的例子和简练的叙述讲解C++编程)

    1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 ...

    C++编程思想习题

    1.5.3对象建模技术(OMT) 1.6为向OOP转变而采取的策略 1.6.1逐步进入OOP 1.6.2管理障碍 1.7小结 第2章 数据抽象 2.1声明与定义 2.2一个袖珍C库 2.3放在一起:项目创建工具 2.4什么是非正常 2.5基本对象 2.6什么是...

    C++编程思想 pdf

    译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 ...

    C++高级参考手册 完全自学 内容详细 讲解通俗易懂

    1.5.3 对象建模技术(OMT) 1.6 为向OOP转变而采取的策略 1.6.1 逐步进入OOP 1.6.2 管理障碍 1.7 小结 第2章 数据抽象 2.1 声明与定义 2.2 一个袖珍C库 2.3 放在一起:项目创建工具 2.4 什么是非正常 2.5 ...

    南方科技大学计算机系C++程序设计.rar

    南科大于仕琪老师的C++课程我是在闲暇时光花了两天时间看完的,(于教授是OpenCV中国论坛管理者,技术高超为人低调,之前也在深圳先进院工作过,也算是我的前辈了,需要好好向前辈学习)我凭借着自己模糊的记忆对每...

    Visual C++ 2005入门经典.part08.rar (整理并添加所有书签)

    2.3.6 ISO/ANSI C++中的基本类型 2.3.7 字面值 2.3.8 定义数据类型的同义词 2.3.9 具有特定值集的变量 2.3.10 指定枚举常量的类型 2.4 基本的输入输出操作 2.4.1 从键盘输入 2.4.2 到命令行的输出 2.4.3 格式化输出 ...

Global site tag (gtag.js) - Google Analytics