打造自己的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指出文章中的錯誤和提出意見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章