介紹
有時,我們需要列出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個單位。
這五類迭代器的從屬關係,如下圖所示,其中箭頭A→B表示,A是B的強化類型,這也說明了如果一個算法要求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指出文章中的錯誤和提出意見。