zoukankan      html  css  js  c++  java
  • STL迭代器------Traits编程技法详细理解(一)

    最近在看STL源码解析的迭代器(iterators)一章,涉及到c++ Traits的编程技法,刚开始看时一头雾水,反复看了好几遍之后才理解这个东西,因此来写写在这方面的理解,如有错误,希望读者指正。

    1.迭代器(iterators)

    在设计模式中,将迭代器进行如下定义:提供一种方法,使之能够依序寻访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式。

    2.迭代器的设计思维

    STL的设计思想在于:将容器和算法分离开来,彼此独立设计,最后在以一帖胶着剂撮合在一起,这个胶着剂就是迭代器。看下图

    难题在于如何设计容器和算法之间良好的胶着剂,也就是迭代器。

    3.迭代器是一种smart point

    迭代器的行为类似指针的对象,而指针最重要的作用是内容提取(deference)和成员访问(member access)。因此,迭代器最重要的编程工作就是operator * 和operator->进行重载。关于这一点,c++标准库有一个auto_ptr可供参考,这是一个包装原生指针的对象,借此可以防止内存泄露(简单来说,使用auto_ptr,不需要delete)

    void func()
    {
    	auto_ptr<string> ps(new string("kobe"));
    
    	cout << *ps << endl;    //输出kobe
    	cout << ps->size() << endl;  //输出5
    	//离开前不需要delete,auto_ptr会自动释放内存
    }
    

    对auto_ptr中的代码进行分析,这里有一份简化的版本:

     1 template<class T>
     2 class auto_ptr
     3 {
     4 public:
     5     explicit auto_ptr(T *p = 0) :pointee(p){};
     6     template<class U>
     7     auto_ptr(auto_ptr<U>& rhs) :pointee(rhs.release()){};
     8     ~auto_ptr(){ delete pointee; }
     9 
    10     template<class U>
    11     auto_ptr<T>& operator=(auto_ptr<U>& rhs)
    12     {
    13         if (this != &rhs) reset(rhs.release());
    14         return *this;
    15     }
    16 
    17     T& operate*() const { return *pointee; }
    18     T* operate->() const { return pointee; }
    19     T* get() const { return pointee; }
    20 
    21     ...
    22 private:
    23     T* pointee;
    24 
    25 };

    对上述代码的简单分析:

    auto_ptr类中,封装了一个原声指针pointee,通过第5行的构造函数进行初始化,然后在析构函数中,去delete pointee,这样就可以避免用户忘记delete而造成的内存泄露。

    auto_ptr中还重载了*和->运算符,实现指针的取值和调用操作,很容易理解,值得注意的是auto_ptr中定义了两个成员函数模板(member template)。第一个是一个构造函数,它接受一个用U进行实例化的auto_ptr对象作为传参,我去查看了auto_ptr的源码,这里的release函数的作用是释放指针,并返回该指针值(假设有一个auto_ptr 对象rhs,rhs.release()作用是返回rhs.pointee的值,然后将rhs.pointee置空,返回的rhs.pointee用来初始化这里的对象的pointee,这样保证了,一块内存区域只有一个指针指向它,避免在析构的时候对该指针进行多次delete而出现错误)。第二个是赋值运算符重载(11~15行),调用reset函数(作用是首先将自己pointee所指向的内存释放,然后重置pointee为rhs.pointee,完成赋值)。

    现在来为list设计一个迭代器,假设list及其节点的结构如下:

    template<typename T>
    class List
    {
        void insert_front(T value);
        void insert_end(T value);
        void display(std::ostream &os = std::cout) const;
        //...
    private:
        ListItem<T>* _end;
        ListItem<T>* _front;
        long _size;
    };
    
    template<typename T>
    class ListItem
    {
    public:
        T value() const{ return _value; }
        ListItem* next()const { return _next; }
        //...
    private:
        T _value;
        ListItem _next;
    };

    如果要为这个List设计一个算法find,首先我们需要为它封装一个迭代器(行为类似指针的外衣),当我们引用提领(deference)这一个迭代器时,传回一个ListItem的对象;当我们递增该迭代器时,它应该指向下一个ListItem对象。为了让该迭代器试用与任何形态的节点,为它设计一个class template

    template<class Item>
    struct ListIter
    {
        Item* ptr;
        ListIter(Item* p = 0) :ptr(p){}
        
        Item& operator*() const(){ return *ptr; }
        Item* operator->() const(){ return ptr; }
    
        //类似++i
        ListIter& operator++()
        {
            ptr = ptr->next;
            return *this;
        }
        //类似i++
        ListIter operator++(int)
        {
            ListIter tmp = *this;
            ++*this;
            return tmp;
        }
    
        bool operator==(const ListIter& i)const
        {
            return ptr == i.ptr;
        }
        bool operator!=(const ListIter& i)const
        {
            return ptr != i.ptr;
        }
    };

    使用迭代器的算法find的函数模板如下:

    template <class InputIterator,class T>
    InputIterator find(InputIterator first, InputIterator last, const T& value)
    {
        while (first != last && *first != value)
        {
            ++first;
        }
        return first;
    }

    现在我们可以将List和find()通过ListIter粘合起来

    void main()
    {
        List<int> mylist;
        for (int i = 0; i < 5; i++)
        {
            mylist.insert_front(i);
            mylist.insert_end(i+2);
        }
        mylist.display();  //10(4 3 2 1 0 2 3 4 5 6)
        ListIter<ListItem<int> > begin(mylist._front());
        ListIter<ListItem<int> > end;
        ListIter<ListItem<int> > iter;
        iter = find(begin, end, 3);
        if (iter == end)
            cout << "not found" << endl;
        else
            cout << "found " << iter->value() << endl;
    }

     由于find函数内以*iter!=value来判断元素值是否吻合,而本例中value的型别是int,iter的型别是ListItem<int>,两者之间并无可使用的operator!=  ,所以这里必须设计一个全局的operator!=重载函数:

    template <typename T>
    bool operator!=(const ListItem<T>& item, T n)
    {
        return item.value() != n;
    }

    从以上的实现可以看出,为了完成一个针对List而设计的迭代器,我们无可避免的暴漏了太多的List实现细节:在main函数中为了制作begin和end,我们暴露了ListItem;在ListIter class中为了达成operator++的目的,我们暴露了ListItem的操作函数next()。

    如果不是为了实现迭代器,listItem原本应该隐藏起来不曝光的。换句话说,要设计出ListIter首先必须对List的实现细节有非常丰富的了解,那么干脆把迭代器的开发工作直接交给List的设计者好了,如此一来,所有实现细节反而得以封装不被使用者看到,这就是为什么每一种STL容器都提供专属的迭代器的缘故。

  • 相关阅读:
    反射 动态导入 元类
    面向对象的继承
    面向对象基础总结
    面向对象基础
    包 logging hashlib copy模块
    os random sys json 模块
    【1012 | Day 43】前端之CSS(下)
    【1011 | Day 42】灵魂拷问:数据放在服务端和客户端的利与弊?
    【1011 | Day 42】socket实现ftp文件的上传和下载
    【1011 | Day 42】前端之CSS(上)
  • 原文地址:https://www.cnblogs.com/yangang92/p/5340314.html
Copyright © 2011-2022 走看看