迭代器设计思路

引言

本篇主要思路总结一下《STL源码剖析》第三章中迭代器设计的知识,看得出是好书,可惜目前还没计划开始看STL源码,第一章allocator直接看不懂,迭代器因为之前项目参考的开源代码有涉及因此只能硬着头皮看,最近有面试问倒了干脆就重新拿出来总结一下。迭代器的设计中一个比较关键的部分是traits设计模式的理解。

指针行为的模拟

迭代器是一种类似指针行为的对象, 因此肯定需要对*解引用运算符和->箭头运算符进行重载. 因外迭代器至少要支持++运算符移动迭代器所指容器中的元素, 以及==, !=, 支持迭代器对所指元素地址的比较, 这5个运算符是迭代器必须重载的. 再次总结一下5个重载运算符的功能

  • *运算符:解引用运算符,返回迭代器指向对象实例,返回类型为指向对象的引用(《C++ Primer》P504)
  • ->运算符:箭头运算符,返回迭代器指向对象指针,返回类型为指向对象指针(《C++ Primer》P505)
  • ++运算符:返回指向容器下一个对象的迭代器,注意++分前置和后置:
    • 前置++返回类型迭代器引用,函数声明形如ListSimpleIter<T>& operator++()(《C++ Primer》P502)
    • 后置++返回类型旧迭代器拷贝,函数声明形如ListSimpleIter<T> operator++(int),多带一个防止重复定义的int参数(《C++ Primer》P503)
  • ++:判断迭代器指向的对象地址是否相等
  • !=:判断迭代器指向对象地址是否不相等

下面给出一个基于《数据结构(邓俊辉)》P69, List数据结构的一个非标准的简单迭代器实现(更像是模拟了指针的行为), 重载了上面5个运算符:

#include "List.h"
namespace TSH_DS
{
    /**
     * @brief List类型迭代器的一个简单实现, 没有任何继承关系
     * 
     * @tparam T List类型的数据类型
     */
    template <typename T>
    class ListSimpleIter
    {
    public:
        /**
         * @brief Construct a new List Simple Iter object
         * List迭代器的默认构造函数, 输入List中迭代器所指节点位置
         * 
         * @param node 
         */
        ListSimpleIter(ListNodePosi(T) node=NULL) : 
        _iter(node)
        {}
        /**
         * @brief 解引用符号函数重载, 默认是返回ListSimpleIter
         * 自身对象引用, 现在需要返回迭代器所示节点内容对象的引用
         * 
         * @return T& 返回当前迭代器指向节点内容的引用
         */
        T& operator*() const
        {return _iter->data;}
        /**
         * @brief 箭头符号函数重载, 默认是返回ListSimpleIter
         * 自身对象地址, 现在需要返回迭代器所示节点内容对象的地址
         * 即等价于:
         * 
         * @code
         * ListSimpleIter iter;
         * iter.operator()->mem;  // 返回T类型的成员mem, 等价于下面的语句
         * (*iter).mem;  // 解引用符号已经被重载, 返回了节点T类型内容的引用, 再取成员mem
         * @endcode
         * 
         * @return T* 返回当前迭代器所指向节点内容的地址
         */
        T* operator->() const
        {
            return &this->operator*();  // 调用解引用运算符
        }
        /**
         * @brief List简单迭代器前置递增符号函数重载
         * 向所指的List下一节点进行移动
         * 
         * @return ListSimpleIter& 
         */
        ListSimpleIter<T>& operator++()
        { _iter = _iter->succ;}
        /**
         * @brief List简单迭代器后置递增符号函数重载
         * 向所指的List下一节点进行移动
         * 
         * @return ListSimpleIter& 
         */
        ListSimpleIter<T>& operator++(int)
        {_iter = _iter->succ;}
        /**
         * @brief 比较相等符号函数重载, 比较的是指向节点的地址
         * 
         * @param iter 
         * @return true 
         * @return false 
         */
        bool operator==(const ListSimpleIter<T> &iter) const
        {return _iter == iter._iter;}
        /**
         * @brief 比较不相等符号函数重载, 比较的是指向节点的地址
         * 
         * @param iter 
         * @return true 
         * @return false 
         */
        bool operator!=(const ListSimpleIter<T> &iter) const
        {return !this->operator==(iter);}
    private:
        ListNodePosi(T) _iter;  ///<@brief 迭代器所指链表位置
    };
} // namespace TSH_DS

简单的一个测试代码如下:

#include <cstdio>
#include <cstdlib>
#include <TSH_DS/List/List.h>
#include <TSH_DS/List/ListSimpleIter.h>
#define TEST_ID 0

/// 以InputIterator为传入模板,T对迭代器指向容器中具体对象类型
template <class InputIterator, class T>
InputIterator myfind(InputIterator first, InputIterator last, const T &value)
{
    while (first != last && *first != value)
        first++;
    return first;
}
int main()
{
    printf("==== Test %2d. Generate a random vector and test simple iterator.\n", TEST_ID);
    TSH_DS::List<int> mylist;
    int num = 10;
    for (int i=0; i<num; i++)
        mylist.insertAsLast(rand()%10);
    for(int i=0; i<mylist.size(); i++)
        printf("%d, ", mylist[i]);
    printf("\n");
    TSH_DS::ListSimpleIter<int> begin(mylist.first());
    TSH_DS::ListSimpleIter<int> end(mylist.last()->succ);  // ListSimpleIter cannot deal with header and tailer
    TSH_DS::ListSimpleIter<int> iter;
    for (int i=0; i<5; i++)
    {
        int target = rand() % 10;
        iter = myfind(begin, end, target);
        if (iter!=end)
            printf("Find target: %2d\n", target);
        else
            printf("Not find target: %2d\n", target);
    }
    return 0;
}

首先,从设计上来说,容器中每个元素可能会对储存对象再进行一次封装,比如上面代码中需要对模板对象T封装一层ListNode, 这种内部对象的数据结构不应该暴露给客户端程序, ListSimpleIter因为遍历节点的需要又不得不使用它, 每个数据结构实现自己遍历的方式又不太一样(可能有自己TreeNode等单位数据结构), 所以迭代器最好是定义和实现在容器的内部, 不同容器迭代器一般不通用。以std::vector为例,我们声明一个std::vector迭代器类型为:

std::vector<int> vec {1, 2, 3};
std::vector<int>::Iterator it = vec.begin();

std::vector<T>::Iterator就是在std::vector<T>中定义的迭代器类型,在STL中每一种容器内部都定义了自己专属的迭代器。

另外,需要注意一下myfind这个函数,接受迭代器类型InputIerator和指向对象类型T,可以发现T就是InputIterator指向的容器中存储对象类型(value_type),和迭代器类型实际上是有很大的关系的,我们将这种和迭代器有很大关系的类型称为:迭代器的相关类型

迭代器traits编程

traits设计模式

traits设计模式主要是为了获得模板类型中的关联类型, 比如迭代器所指容器中元素数据的类型(value_type), 当迭代器对于STL算法作为一个模板template <class I>传进来的时候, 我们对于模板类I一无所知,甚至编译器只有在使用该模板函数的时候才会对其实例化,迭代器I所指对象类型即value_type我们是不知道的, 最简单的方法就是在迭代器中定义一个对数据类型的typedef

在这里插入图片描述
然后在调用迭代器的泛型算法中,我们不直接获得迭代器的typedef, 而是用一个iterator_traits, 以迭代器类型作为模板I输入, 通过typedef提取出value_type:

在这里插入图片描述
typedef中的typename显示指明了这是一个嵌套类型(模板I没有实例化,我们也不能知道value_type是类型中定义的类型别名还是静态成员).
为什么看似多此一举, 不是使用iterator中自己定义的iterator::value_type,而是需要使用iterator_traits先封装一次, 再转换给STL算法, 是因为iterator_traits能够保证输入STL算法的类型为指针时也能正常运行, 为此, iterator_traits对上面定义的模板做了一个进一步的限制, 为普通指针类型专门 偏特化(partial specialization) 出来一个版本:

在这里插入图片描述
由此iterator_traits也能够提取出普通指针的关联类型value_type, 这时候到STL算法中, 先使用iterator_traits提取出输入迭代器/普通指针模板中的关联类型, 再使用这些类型进行数据比较的操作, 就没有问题了:

在这里插入图片描述

iterator_traits涉及的关联类型

Iteratoriterator_traits中定义的相关类型当然不止value_type,常用的关联类型一共有5种,换句话说,一个符合规范的iterator中必须定义自己的以下几种关联类型:

template <class I>
class iterator_traits<I> {
	typedef typename I::value_type			value_type;
	typedef typename I::difference_type		difference_type;
	typedef typename I::pointer				pointer;
	typedef typename I::reference			reference;
	typedef typename I::iterator_catergory	iterator_catergory;
};

template <class T>
class iterator_traits<T*> {
	typedef T							value_type;
	typedef ptrdiff_t					difference_type;
	typedef T*							pointer;
	typedef T&							reference;
	typedef random_access_iterator		iterator_catergory;
};

value_type

这个没什么好说的, 就是迭代器所指数据的类型, 比如:

std::vector<int>::iterator::value_type == int

difference_type

表示两个迭代器之间距离的类型, STL算法count()提供计数功能, 那么返回的就是这个类型:
在这里插入图片描述
对于C++内置指针的距离类型ptrdiff_t,<cstddef>头文件中, 普通指针和const指针的iterator_traits的特偏化也是直接定义了这个类型:
在这里插入图片描述

reference_type

迭代器所指数据类型的引用类型, 这个一般就是T&了, 对于普通指针和const指针iterator_tarits会有区别
普通指针T中iterator_traits的偏特化reference_typetypedef为:

typedef T& reference_type

const指针中为了防止所指数据返回引用被修改, 定义为:

typedef const T& reference_type

pointer_type

reference_type差不多, 对于一般数据类型的指针类型是T*, 普通指针和const指针iterator_tarits会有(顶层)const之分:

typedef T* pointer_type
typedef const T* poiner_type

iterator_cartegory

用于表示迭代器的类型,这是由具体迭代器所属容易特性来决定的, 非常重要, 决定了STL算法重载的函数以及程序运行的效率, 一共有5种:

  • input_iterator: 只读迭代器(不许被外部程序改变)
  • ouput_iterator: 只写迭代器
  • forward_iterator: 前向迭代器, 只允许++操作符 (forward_list)
  • bidirectional_iterator: 双向迭代器, 允许++, --操作符 (list)
  • random_access_iterator: 随机访问迭代器, 允许++, --, p+n, p-n, p[n], p1<p2

五个迭代器具有如下的继承关系(最顶上是父类):
在这里插入图片描述
STL针对不同的迭代器类型进行函数的重载, 而不是利用标志位进行判断(比如说函数内使用if或者switch判断具体执行版本), 这可以在编译时而不是运行时决定调用函数的版本, 以提高效率. 重载决断是根据输入形参的类型来决断的(重载决议最佳匹配原则,《C++ Primer》P209), 因此需要为上面5种迭代器类型定义成标记用的类型(tag types):
在这里插入图片描述
在STL算法中, 具体需要重载的算法(比如__advance P95), 会把tag_types作为一个占位类型输入, 仅用来判断重载的函数版本, 最后使用一个不带tag_types的函数(比如advance P96)封装重载算法的接口.
tag_types使用了上图相同的继承关系图, 这可以防止“单纯传递调用的函数”(说人话不用为每一种iterator_cartergory标志类型都定义重载类型,没有定义对应重载类型的标记类型可以向上转型调用父类的重载版本), 比如__advance可以不一定forward_iteration_tag, 因为它可以兼容父类input_iterator_tag的重载版本.

std::iterator

对于每一个想要兼容STL算法的迭代器, 上面5个关联类型必须都要提供, 自定义的iterator需要继承std::iterator, 帮助补全默认的嵌套关联类型:
在这里插入图片描述
std::iterator里面啥也没有, 就是typedef了5个嵌套的关联类型. 其中 iterator_categoryvalue_type必须是用户显式给到继承的std::iterator模板中的 。 这样定义自己的迭代器时候通过std::iterator模板类实例化后继承, 这些关联类型就不用再typedef了. 当然, 必要的操作符: ++, ==, !=, --等等运算符还是要自定义的, 具体他们的实现这是由容器本身的特性决定的.

总结

迭代器traits设计框架是怎么样的

迭代器的traits萃取了迭代器的五种关联类型:

  • value_type: 迭代器指向数据的类型
  • difference_type: 迭代器间距离类型, 默认是指针的距离类型ptrdiff_t
  • reference_type: 数据引用类型T&, 注意防止修改数据的const T&类型
  • pointer_type: 数据指针类型T&, 注意防止数据修改的const T* 类型
  • iterator_cartegory: 迭代器类型, 一共5种, 4层继承关系: 只读/只写–>前向–>双向–>随机

首先, 对于一个STL算法兼容的迭代器, 必须包含上面5个关联类型的声明, 即必须有内嵌类型:

typename IteratorType::value_type
typename IteratorType::difference_type
typename IteratorType::reference_type
typename IteratorType::pointer_type
typename IteratorType::iterator_cartegory

这个可以通过继承std::iterator类完全补全声明,以满足STL泛型算法对于迭代器调用的规范:

template <T>
class IteratorType: public std::iterator<<std::forward_iterator_tag, T>
{...};

注意定义的IteratorType中必须至少重载*, ->, ++, ==, !=操作,一般来说它最菜也是一个前向迭代器.

对于一个STL的泛型算法, 首先会将迭代器作为模板接受, 可以指定具体允许到哪种迭代器类型iterator_catergory, 以std::find声明为例:

template <class InputIterator, class T>
   InputIterator find (InputIterator first, InputIterator last, const T& val);

对于可以通过不同迭代器类型进行优化的情况(advance), 可以在原型实现中使用重载决议iterator_catergory作为参数进行重载
在泛型算法中, 如果要使用迭代器的关联类型, 可以使用std::iterator_traits<I>萃取, 比如上面的iterator_catergory参数:

iterator_traits<IteratorType>::iterator_catergory

为了让支持迭代器的泛型算法同时支持指针, std::iterator_traits<I>有对原始指针的特偏化声明:

template< class T >
struct iterator_traits<T*>;

为什么要迭代器

在泛型算法使用迭代器进行操作时, 需要使用到其相关的类型, 比如指向数据的类型, 迭代器本身的类型(是否支持随机访问, 是否支持后向遍历…), 这些需要迭代器类型内部对其typedef, 然后使用typename Iterator<...>::XXX进行访问.
但是泛型算法应该支持原始指针, 原始指针里是没有这些关联类型的, 因此需要traits,
一方面traits会通过typedef I::XXX XXX的方法萃取迭代器的相关类型, 通过iterator_traits<I>::XXX获得类型
另一方面, 使用template< class T > struct iterator_traits<T*>;对指针进行特偏化, 通过iterator_traits<T*>::XXX也能获得对应的相关类型

泛型算法如何使用迭代器类型调用不同的算法实现

使用重载决议编译时确定, 而不是if...else...这种运行时判断, 可以加速STL的效率. 具体的, 在底层函数声明时, 将迭代器类型作为形参列表的一个参数, traits获得即iterator_traits<IteratorType>::iterator_catergory, 不同的迭代器类型可以调用不同的重载实现. 获得最大的效率

参考文献

  1. iterator_traits文档
  2. std::iterator文档
  3. 《STL源码剖析》
  4. 《C++ Primer》
  5. 《数据结构(C++语言版)》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
## C#下Lua编程支持 xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。 ## xLua的突破 xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是: * 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现; * 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc; * 编辑器下无需生成代码,开发更轻量; 更详细的特性、平台支持介绍请看[这里](Assets/XLua/Doc/features.md)。 ## 安装 打开zip包,你会看到一个Assets目录,这目录就对应Unity工程的Assets目录,保持这目录结构放到你的Unity工程。 如果希望安装到其它目录,请看[FAQ](Assets/XLua/Doc/faq.md)相关介绍。 ## 文档 * [常见问题解答](Assets/XLua/Doc/faq.md):常见问题都总结在这里,初使用大多数问题都可以在这里找到答案。 * (必看)[XLua教程](Assets/XLua/Doc/XLua教程.md):教程,其配套代码[这里](Assets/XLua/Tutorial/)。 * (必看)[XLua的配置](Assets/XLua/Doc/configure.md):介绍如何配置xLua。 * [热补丁操作指南](Assets/XLua/Doc/hotfix.md):介绍如何使用热补丁特性。 * [XLua增加删除第三方lua库](Assets/XLua/Doc/XLua增加删除第三方lua库.md):如何增删第三方lua扩展库。 * [XLua API](Assets/XLua/Doc/XLua_API.md):API文档。 * [生成引擎二次开发指南](Assets/XLua/Doc/custom_generate.md):介绍如何做生成引擎的二次开发。 ## 快速入门 一个完整的例子仅需3行代码: 安装好xLua,建一个MonoBehaviour拖到场景,在Start加入如下代码: ```csharp XLua.LuaEnv luaenv = new XLua.LuaEnv(); luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')"); luaenv.Dispose(); ``` 1、DoString参数为string,可输入任意合法的Lua代码,本示例在lua里调用C#的UnityEngine.Debug.Log打印了个日志。 2、一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一。 C#主动调用lua也很简单,比如要调用lua的系统函数,推荐方式是: * 声明 ```csharp [XLua.CSharpCallLua] public delegate double LuaMax(double a, double b); ``` * 绑定 ```csharp var max = luaenv.Global.GetInPath("math.max"); ``` * 调用 ```csharp Debug.Log("max:" + max(32, 12)); ``` 建议绑定一次,重复使用。生成了代码的话,调用max是不产生gc alloc的。 ## 热补丁 * 侵入性小,老项目原有代码不做任何调整就可使用。 * 运行时影响小,不打补丁基本和原有程序一样。 * 出问题了可以用Lua来打补丁,这时才会走到lua代码逻辑; [这里](Assets/XLua/Doc/hotfix.md)是使用指南。 ## lua5.3 vs luajit xLua有两个版本,分别集成了lua5.3和luajit,一个项目只能选择其一。这两个版本C#代码是一样的,不同的是Plugins部分。 lua5.3的特性更丰富些,比如支持原生64位整数,支持苹果bitcode,支持utf8等。出现问题因为是纯c代码,也好定位。比起luajit,lua对安装包的影响也更小。 而luajit胜在性能,如果其jit不出问题的话,可以比lua高一个数量级。目前luajit作者不打算维护luajit,在找人接替其维护,后续发展不太明朗。 项目可以根据自己情况判断哪个更适合。因为目前lua53版本使用较多,所以xLua工程Plugins目录下默认配套是lua53版本。 ## 更多示例 * [01_Helloworld](Assets/XLua/Examples/01_Helloworld/):

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值