C++ Traits编程技法--从迭代器的设计看参数推导与类型推导

简介:

迭代器与相应类型推导


在写作泛型函数或代码时,我们可能存在这样的需要:与参数相关的其它类型,比如一个迭代器的值的类型,在算法中运用迭代器时,很可能会用到其也叫相应类型(associate type)。

什么是相应类型?
迭代器所指之物的类型就是其中一个。如果我们的算法中有必要声明一个变量,以”迭代器所指对象的类别”为型号。

本文要向大家展示一个函数模板推导机制使用技法,这个在STL的迭代器和许多排序算法中广泛使用。

考虑一个情况,我们在写一个泛型函数,它接受一对迭代器,要做的事就是对这一对迭代器之间的元素进行排序,其中将出现这幕:我需要对两个值进行交换。不知道大家有没有写过这样的代码,现在的问题是如何实现这两个值的交换?

如:

if(*itr > *(itr+1))
{
    //交换两个迭代器指向的值
    //注意此时我们并不(*itr)的类型
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

很显然我们需要了解到迭代器所指向的具体类型,这该怎么好呢

毕竟如果C++没有只支持sizeof(),并不支持typeof()

有些人说了我们有RTT1机制中的typeid(),是的很好,但是那获取到的也只是类型名称name,而不是类型type,更不可能来做变量声明

关于RTT1机制中的typeid(),请参见C++ typeid关键字详解

解决方法一函数模版的参数推导机制


解决的方法:利用函数模版(function template)的参数推导(argument deducation)机制。

示例


///理解模板类型推导
#include <iostream>

template <class Iter, class T>
void func_impl(Iter iter, T t)

{
    T tmp;              //  这里解决了问题, T就是迭代器所指之物的类型, 本例为int

    /// ...  这里做原来func应该做的全部工作
}




template<class Iter>
inline func(Iter iter)
{
    func_impl(iter, *iter);
}


int main( )
{
    int i;
    func(&i);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

解析


参数推导机制,以func()为对外接口,却把实际的操作全部置于func_impl()之中,由于func_impl()是一个函数模版(function template),一旦被调用,编译器会自动进行template的参数推导,于是导出类型T的问题,顺利得到了解决。

问题


但是迭代器相应类型不只是“迭代器所指对象的类型”一种而已

根据经验,最常用的相应类型有5种,然而并非任何情况下任何一种都可利用函数模版的参数推导机制来取得。

因此我们需要更一般的解法。

解决方法二使用嵌套依赖类型(nested depended name)


也称为Traits编程技法,是STL中广泛使用的技巧

迭代器所指对象的类型,称为迭代器的value type,上述的参数类型推导技巧虽然可用于value type,却并不对所有的相应类型可用。

前面的情况,通过隐藏了中间层,通过函数模版自己去推导参数类型,我们的接口也只是关心迭代器而不在意其指向的,但是如果我们的对外接口不能不了解到其相应类型的时候,这种方法就不适用了。

比如如下情况,万一value type必须用于函数的返回值,就束手无策了。

毕竟函数的“template参数推导机制”推而导之的只是参数,无法推导函数的回返值类型。

我们需要其他方法,有什么好的方法呢,还记得之前提到的typename的以及嵌套依赖类型(nested depended name)么。

那不就可以么,我们不妨一试

参见 C++中typename和class在声明模板时的区别

示例


#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

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



    typedef T value_type;       //  内嵌类型声明{nested type}
    T   *m_ptr;
};


template <class Iter>
typename Iter::value_type          ///  这一整行func的返回值类型
func(Iter iter)
{
    ///
    return *iter;
}


int main(void)
{
    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

解析


func()的返回值被定义为typename Iter::value_type

内嵌类型被定义为 typedef T value_type

由于T是一个template参数,在它被编译器具体化之前,编译器对T一无所知,换句话说,编译器此时并不知道MyIter<T>::value_type代表的是一个类型还是一个成员函数或者说是一个数据成员,

而这个typename关键字的用意就在于告诉编译器这个是一个类型,如敝才能顺利通过编译。

问题


这样我们的问题似乎得到了解决,但是这里有一个隐晦的陷阱:并不是所有的迭代器都是类class type,比如有原生的指针就不是。如果不是class type,就无法为它定义内嵌类型。

但是这显然不是我们做模版类所期待的,在STL(甚至于整个泛型思维)绝对接受原生的指针作为一种迭代器,所有我们上面的方法还不够,有没有什么方法可以让上述的一般化概念针对特定情况(比如原生的指针类型)做特殊化处理呢?

特性萃取机器–偏序化实现支持原生类型


那么这就是template partial specialization可以完成的

模版特化问题


如果class template拥有一个以上的template参数,那么可以针对其中某个(或者多个,但是不能全部)的参数进行特化工作。

换句话说,我们可以在泛化设计中提供一个特化的版本。即通过将泛化版本中的某些template参数赋予明确的指定。

/// 泛化的C接收任意类型
template <typename T>
class C             //  这个泛化版本允许接受T为任意类型
{
    // NOP...
};


/// 特化的类C接收原生的指针作为对象
template <typename T>
class C<T*>             //  这个泛化版本适用于"T为原生指针的情况"
{
    //  T为原生指针便是T为任何型别的一个更进一步的条件限制
    // NOP...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

关于函数模版的特化问题,不是我们今天的重点,要了解详细信息,请参见
C++模板的特化详解(函数模版特殊,类模版特化)

通过针对任何template参数的模版,进行特化,得到一个限制了参数对象的特化版本。

有了这个利器,我们就可以解决前述的”内嵌类型”未能解决的问题,先前的问题是,原生的指针并非class,因此无法为他们定义内嵌型别。

现在我们可以通过对迭代器指针为参数的特化对象,来设计可以接收原生指针的特殊迭代器。

万能的中间层

因此我们设计出一个中间层,并添加一个value type内嵌对象

//  泛化的iterator_traits对象
template <typename Iter>
struct iterator_traits
{
    typedef typename I::value_type value_type;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这个所谓的traits迭代器说明,如果Iter定义有自己的value type,那么就通过这个traits的作用,萃取出来的value_type成员就是Iter::value_type。

换句话说,如果I定义有自己的value type,那么先前的那个func就成为

//  之前的func
template <class Iter>
typename Iter::value_type          ///  这一整行func的返回值类型
func(Iter iter)
{
    ///
    return *iter;
}


//  新的func
template <class Iter>
//typename Iter::value_type          ///  这一整行func的返回值类型
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

特化中间层以支持原生指针


增加iterator_traits这一中间层,现在我们为iterator_traits添加特化版本。

// 特化的iterator_traits接收<T*>参数
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过这种方式,原生的指针比如int *虽然不是类对象class type,亦可通过traits取其value type。

于是我们的迭代器支持原生的指针类型了。

但是这样子够了么,请看下面的例子,

当我们使用指向const常数对象的指针时,看看发生了什么

iterator_traits<const int *>::value_type
  • 1
  • 1

获得到的是一个const int,而不是int。很显然这个并不是我们所期望?

比如我们利用这种方式来声明一个临时变量,使其类别与迭代器的value type相同,而现在,声明一个无法赋值(因const之故)的暂时变量,并没有什么卵用。

这一切的一切,就只是因为我们获取到的迭代器的value type是一个pointer-to-const

进一步特殊const


// 特化的iterator_traits接收<const T*>参数, 萃取出一个T型
template<class T>
struct iterator_traits<const T *>   /
{
    typedef T value_type;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

现在OK了,不管是针对迭代器MyIterm,还是原生的指针int ,甚至是const int ,都可以通过traits取出正确的value type

代码


///c++11 条款1:理解模板类型推导
///http://blog.csdn.net/coolmeme/article/details/43986163
///http://blog.csdn.net/shinehoo/article/details/5722362
///  STL源码剖析 PDF-119/534

#include <iostream>
#include <typeinfo>


#include <iostream>
template <class T>
struct MyIter
{
    MyIter(T *p = NULL)
    :m_ptr(p)
    {
        /// NOP...
    }

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



    typedef T value_type;       //  内嵌型别声明{nested type}
    T   *m_ptr;
};


///// 泛化的C接收任意类型
//template <typename T>
//class C             //  这个泛化版本允许接受T为任意类型
//{
//    // NOP...
//};
//
//
///// 特化的类C接收原生的指针作为对象
//template <typename T>
//class C<T*>             //  这个泛化版本适用于"T为原生指针的情况"
//{
//    //  T为原生指针便是T为任何型别的一个更进一步的条件限制
//    // NOP...
//};


//  泛化的iterator_traits对象
template <typename Iter>
struct iterator_traits
{
    typedef typename Iter::value_type value_type;
};

// 特化的iterator_traits接收<T*>参数, 萃取出一个T类型
template<class T>
struct iterator_traits<T *>
{
    typedef T value_type;
};

// 特化的iterator_traits接收<const T*>参数, 萃取出一个T型
template<class T>
struct iterator_traits<const T *>
{
    typedef T value_type;
};


template <class Iter>
//typename Iter::value_type          ///  这一整行func的返回值类型
typename iterator_traits<Iter>::value_type
func(Iter iter)
{
    ///
    return *iter;
}



int main(void)
{

    MyIter<int> ite(new int(8));
    std::cout <<func(ite) <<std::endl;


    std::cout <<typeid(iterator_traits< MyIter<int> >::value_type).name();
    std::cout <<typeid(iterator_traits<int *>::value_type).name();
    std::cout <<typeid(iterator_traits<const int *>::value_type).name();


    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

最后通过traits这个特性萃取机角色,可以巧妙的萃取出各个迭代器的属性。

STL中迭代器的设计


这点我们可以在STL源码的找到对应的设计,参见 stl_iterator_base.h
由于

#ifdef __STL_USE_NAMESPACES
template <class _Category, class _Tp, class _Distance = ptrdiff_t,
          class _Pointer = _Tp*, class _Reference = _Tp&>
struct iterator {
  typedef _Category  iterator_category;
  typedef _Tp        value_type;
  typedef _Distance  difference_type;
  typedef _Pointer   pointer;
  typedef _Reference reference;
};
#endif /* __STL_USE_NAMESPACES */

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION

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   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};

template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};


转载:http://blog.csdn.net/gatieme/article/details/50950726

目录
相关文章
|
2月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
281 63
|
2月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
161 7
|
2月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
53 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
86 11
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
47 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
83 2
|
7天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
22 2
|
13天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
41 5
|
19天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
49 4
|
21天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
47 4