介绍
有时,我们需要列出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指出文章中的错误和提出意见。