打造自己的Iterator Adapter – File Iterator

介绍

有时,我们需要列出Windows中某个文件夹里的文件。按照常规的方法就是把搜索到的文件在某个链表的尾端插入。例如下面的伪码所示

void list_file(const std::string& path, file_list& flist)

{

     while(判断path是否有文件)

     {

           filst.push_back(文件名);

           查找下一个文件;

}

}

 

list_file这个算法虽然能正常工作,但是它和数据容器file_list紧密相联,形成极高的耦合。如果,现在又要搜索某个文件夹里的指定文件,那么就需要用list_file进行全盘搜索或重新再设计一个算法。

对于这样的设计,我们先回归到STL上,STL为我们展示了C++程序设计的新思维。迭代器(Iterators)是一种抽象概念,这种模式提供了一种方法来遍历某个聚合物中的所有元素,而又不暴露出聚合物内部的细节。而STL中的迭代器,将数据容器和算法分开,而达到算法的泛化。更重要的是,它提供了一个统一的形式来让程序对不同数据结构进行操作。

那么我们可以将一个文件夹抽象为数据容器,而里面的子文件夹和文件则抽象为元素。这样一来,就可以设计一种迭代器来遍历文件夹中的文件,如果设计一个符合STL规范的迭代器,还可以与STL算法完美结合,从而利用STL提供的算法。

 

设计目标

看看下面的代码

void display(const file_info& x)

{

       std::cout<<(x.is_dir()?" * ":"   "); //输出* 表示这是一个文件夹

       std::cout<<x.file_name()<<std::endl;

}

 

int main()

{  

   std::for_each(file_iterator<file_info>("c://windows"), file_iterator<file_info>(), display);

}

 

我们还可以将文件列表放到一个容器中

std::vector<file_info> vec_file;

std::copy(file_iterator<file_info>("c://windows"), file_iterator<file_info>(), std::back_inserter(vec_file));

对于设计符合STL规范的迭代器,我们有必要来回忆一下关于STL中迭代器的概念。

 

Let’s get started

事实上,迭代器并不是一个单纯的概念,STL根据不同的操作情况,将迭代器分为五类:

Input Iterator 只能单步向前迭代元素,不允许修改由该类迭代器引用的元素。

Output Iterator 该类迭代器和Input Iterator极其相似,也只能单步向前迭代元素,不同的是该类迭代器对元素只有写的权力。

Forward Iterator 该类迭代器可以在一个正确的区间中进行读写操作,它拥有Input Iterator的所有特性,和Output Iterator的部分特性,以及单步向前迭代元素的能力。

Bidirectional Iterator 该类迭代器是在Forward Iterator的基础上提供了单步向后迭代元素的能力。

Random Access Iterator 该类迭代器能完成上面所有迭代器的工作,它自己独有的特性就是可以像指针那样进行算术计算,而不是仅仅只有单步向前或向后迭代。iter + n,就表示以常量时间向后移动到n个单位。

 

这五类迭代器的从属关系,如下图所示,其中箭头AB表示,AB的强化类型,这也说明了如果一个算法要求B,那么A也可以应用于其中。

开始设计

先从上面五类迭代器中选出一种来说明我们即将设计的iterator,结合实际情况,我们的目的是获得某个目录下的文件,那么Input Iterator就是我们所选择的。

一个Input Iterator的要求是

1、 可默认构造的:提供默认构造函数

2、 可复制的:提供拷贝构造函数 拷贝赋值操作符

3、 可比较的:提供operator==,和operator!=

4、 单步向前迭代能力:提供前置自增运算符 后置自增运算符

5、 解引用能力:提供operator*

如何构造一个指向尾端的迭代器?可以借用IStream Iterators的思想,默认构造一个该迭代器的对象,就表示指向尾端的迭代器。

如果在文件迭代过程中由Windows引发了错误,如何处理呢?对于将要设计的文件迭代器来说,并没有写入操作,随时可以退出迭代,而Windows引发的错误却有必要传递给调用者,但是在这里用传统的返回值传递是不可能的,因为iterator是一个对象,而不是函数的调用,因此,我们可以考虑用异常。抛出一个file_exception的异常对象。

关于文件迭代器的value_type。由于文件的信息是由Windows提供,而Windows将文件的信息都在一个WIN32_FIND_DATA结构对象中。为了把文件信息传递给value_type的对象,那么就有必要为value_type提供一个T(const WIN32_FIND_DATA&)的构造函数。

资源问题,对文件的搜索会使用Windows返回的句柄,这个系统对象的句柄并不像C++中内存资源那样可以进行拷贝构造,而即将设计的文件迭代器是可以被拷贝构造的,这就意味着多个文件迭代器会引用同一个系统对象,这样一来,当文件迭代器发生析构时,就无法正确地判断是否要释放当前的系统对象,如何解决这个问题呢?我们可以在文件迭代器中设计一个局部的类 file_operate_raii,通过RAII技巧,并加入引用记数就可以顺利解决这个问题。

线程安全问题,由于上面的提到的file_operate_raii其实就是一个被多个文件迭代器共读写的对象,那么我们就有必要考虑到线程安全的问题。

 

代码就是设计,下面来看看实现的C++代码

//file: file_iterator.h

#ifndef _FILE_ITERATOR_H

#define _FILE_ITERATOR_H

 

#include<windows.h>

#include<string>

#include<exception>

 

namespace f_iter

{

class file_exception

      :public std::exception

{

public:

    const char* what() const throw()

    {

       return "An error was occurred";   

    }

};

 

template<typename _Value_Type>

class file_iterator

    :public std::iterator<std::input_iterator_tag, _Value_Type>

{

public:

      file_iterator(const std::string& file_path)

              :end_(false), file_path_(file_path), hfind_(0), p_fo_raii_(0)

      {

          _m_prepare();

      }

     

      file_iterator():end_(true), hfind_(0),p_fo_raii_(0){} //默认构造

     

      file_iterator(const file_iterator& x) //可拷贝

          :end_(x.end_), file_path_(x.file_path_), hfind_(x.hfind_), p_fo_raii_(x.p_fo_raii_), value_(x.value_)

      {

          if(p_fo_raii_)

               p_fo_raii_->increase();   

      }

     

      file_iterator& operator=(const file_iterator& x) //可拷贝

      {

           if(this == &x) return *this; //防止自我拷贝

           end_ = x.end_;

           file_path_ = x.file_path_;

           hfind_ = x.hfind_;

           value_ = x.value_;

           if(p_fo_raii_)

           {

              p_fo_raii_->decrease();

              if(p_fo_raii_->empty())

              {

                  delete p_fo_raii_;

              }

           }

           p_fo_raii_ = x.p_fo_raii_;

           if(p_fo_raii_)

               p_fo_raii_ ->increase();

           return *this

      }

     

      ~file_iterator()

      {

         if(p_fo_raii_)

         {

            p_fo_raii_->decrease();

            if(p_fo_raii_->empty())

            {

                delete p_fo_raii_;

                p_fo_raii_ = 0;

            }   

         }     

      }

public:

      const _Value_Type& //对元素只读,所以返回const

      operator*() const { return value_; }

 

      const _Value_Type*

      operator->() const { return &(operator*()); }

 

      file_iterator&   //单步向前迭代的能力

      operator++()

      { _m_read(); return *this; }

 

      file_iterator

      operator++(int) 

      {

             file_iterator tmp = *this;

             _m_read();

             return tmp;

      }

     

      bool equal(const file_iterator& x) const

      {

          if(end_ && (end_ == x.end_)) return true;

          return (strcmp(find_file_data_.cFileName, x.find_file_data_.cFileName) == 0

                  &&

                  file_path_ == x.file_path_);   

      }

     

private:

    void _m_prepare()

    {

        if(p_fo_raii_ == 0)

              p_fo_raii_ = new file_operate_raii(file_path_, find_file_data_);

        hfind_ = p_fo_raii_->get_handle();

        if(hfind_ == INVALID_HANDLE_VALUE)

        {

              end_ = true;

              hfind_ = 0;

              return;

        }   

        if(*find_file_data_.cFileName=='.')

        {

              ::FindNextFile(hfind_, &find_file_data_);

              if(::FindNextFile(hfind_, &find_file_data_) !=0 )

                     value_ = _Value_Type(find_file_data_); //_Value_Type的要求有T(const WIN32_FIND_DATA&)的构造函数

              else

              {

                 end_ = true;

                 hfind_ = 0;

                 if(ERROR_NO_MORE_FILES != ::GetLastError())

                       throw file_exception();   

              }

        }

        else

        {

              value_ = _Value_Type(find_file_data_);

        }  

    }

   

    void _m_read()

    {

           if(FindNextFile(hfind_, &find_file_data_) !=0 )

           {

                 value_ = _Value_Type(find_file_data_);  //_Value_Type的要求有T(const WIN32_FIND_DATA&)的构造函数

           }

           else

           {

                 end_ = true;

                 hfind_ = 0;                

                 if(ERROR_NO_MORE_FILES != ::GetLastError())

                       throw file_exception();

           }

    }

private:

    //class lock解决线程安全问题

    class lock{

    public:

         lock(){

            ::InitializeCriticalSection(&cs_);

         }

         ~lock(){

            ::DeleteCriticalSection(&cs_);

         }

         void enter() const{

            ::EnterCriticalSection(&cs_);

         }

         void leave() const{

            ::LeaveCriticalSection(&cs_);

         }

    private:

         mutable ::CRITICAL_SECTION cs_;

    };   

   

    //class file_operate_raii 负责系统对象的创建和释放

    class file_operate_raii

    {

        file_operate_raii(); //non-default-constructable

        file_operate_raii(const file_operate_raii&); //non-copyable

        file_operate_raii& operator=(const file_operate_raii&); //non-copyable

    public:

       

        file_operate_raii(const std::string& x, WIN32_FIND_DATA& y)

             :counter_(1)

        {

            handle_ = ::FindFirstFile((x + "//*").c_str(), &y);  

        }

       

        ~file_operate_raii()

        {

                ::FindClose(handle_);

        }

    public:

        void increase()

        {

            lock_.enter();

            counter_++;

            lock_.leave();

        }

       

        void decrease()

        {

            lock_.enter();

            counter_--;

            lock_.leave();

        }

       

        bool empty() const

        { return counter_ == 0; }      

        size_t count() const

        {  return counter_;   }

       

        ::HANDLE get_handle() const

        {  return handle_;    }

    private:

        ::HANDLE  handle_;

        size_t    counter_;

    };

private:

    bool              end_;

    _Value_Type      value_;

    std::string      file_path_;

    WIN32_FIND_DATA find_file_data_;

    ::HANDLE          hfind_;

    file_operate_raii* p_fo_raii_;

};

 

template<typename _Value_Type>

inline bool operator==(const file_iterator<_Value_Type> & x, const file_iterator<_Value_Type> & y)

{

   return x.equal(y);   

}

 

template<typename _Value_Type>

inline bool operator!=(const file_iterator<_Value_Type> & x, const file_iterator<_Value_Type> & y)

{

   return !x.equal(y);   

}

}

 

#endif

 

上面一大堆代码,您也许看出一点东西来了。因为多个file_iterator对象可以引用同一个系统对象,换句话说就是,比如两个file_iterator对象a,b

 a = b;

 if(++a == ++b){} //这个判断不会被成立

是的,Input Iterator有这个特性!

 

最后,我们来使用这个file_iterator

#include"file_iterator.h"

#include<algorithm>

#include<iostream>

using namespace std;

class file_info

{

public:

    file_info() :is_dir_(false)

    {}

    file_info(const WIN32_FIND_DATA& x) //file_iterator要求这个构造函数

         :is_dir_((FILE_ATTRIBUTE_DIRECTORY & x.dwFileAttributes) == FILE_ATTRIBUTE_DIRECTORY), file_name_(x.cFileName)

{}

 

public:

    std::string file_name() const

{   return file_name_;   }

 

    bool is_dir() const

    {   return is_dir_;    }

private:

    bool          is_dir_;

    std::string file_name_;

};

 

void display(const file_info& x)

{

       std::cout<<(x.is_dir()?" * ":"   ");

       std::cout<<x.file_name()<< std::endl;

}

 

int main()

{

   using namespace std;

   using namespace f_iter;

   for_each(file_iterator<file_info>("c://windows"), file_iterator<file_info>(), display);

}

 

对于这个设计可能存在BUG,欢迎各位看官指出来,同时如果您有什么想法或意见,我希望各位通过电子邮件来开导我! 非常感谢 boxban指出文章中的错误和提出意见。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值