我编写了一个非常简单的文件管理数据库,基本上看起来像这样:
class FileDB
{
public:
FileDB(std::string dir) : rootDir(dir) { }
void loadFile(std::string filename, File &file) const;
void saveFile(std::string filename, const File &file) const;
private:
std::string rootDir;
}
现在我想遍历数据库中包含的所有文件,例如使用std::iterator
:
void iterateFiles()
{
FileDB filedb("C:\\MyFiles");
for (FileDB::iterator file_it = filedb.begin(); file_it != filedb.end(); ++file_it)
{
File f = *file_it;
// do something with file
}
}
我已经阅读了类似问题的答案,有些人建议派生std::iterator
,有些人使用std::iterator_traits
,但我真的不明白如何做到这一点。尝试实现自定义迭代器时可能出现什么问题?什么是一种简单而优雅的方式呢?
修改 请不要考虑使用boost,我的问题更具概念性。
编辑2:
FileDB的工作原理如下:
ROOTDIR
foo2的
FOON
barM中
所以基本上,我可以找到一个名字的文件。
由于我的容器不在内存中,因此我没有指向其数据的指针。所以我的想法是将文件的路径存储在迭代器中。这样,我可以使用字符串比较来实现operator==
,因为路径应该是唯一的。从fileDB.end()
返回的迭代器将是一个空字符串,operator*
将使用其文件路径调用fileDB::loadFile()
。
我最关心的是operator++
。有了文件名,我可以找到包含目录并搜索下一个文件,但这实际上是无效的。有关如何做到这一点的任何想法?或者我对整个概念完全错了?
答案 0 :(得分:12)
自己编写迭代器几乎不可能。向类中添加迭代器的最简单方法是重用现有的迭代器。你应该知道,例如,指针和C ++中的迭代器一样好,所以有很多方法可以提供迭代器而不需要自己编写。
这基本上是C ++在很多方面的工作方式。它试图通过给图书馆作者带来很多负担,使最终用户的语言易于使用和简单化。即库编写者可以编写所有不起眼的东西,因此最终用户不必这样做。迭代器通常是库的一部分。
话虽如此,实际上是丑陋的部分:
为了能够编写自己的迭代器,这里有一些你需要注意的事情。
类型特征:
类型特征是一种向C ++中的类型添加附加信息的简单机制,即使对于无法自行更改的类型也是如此。例如,对于迭代器,重要的是知道它迭代的内容(即包含的类型)。获取给定迭代器的这些信息的方法很大程度上取决于迭代器。对于实际上是对象的迭代器,您可以在类中添加typedef并使用它们,但对于作为指针的迭代器,您需要从指针类型推断它。为了实现这一点,信息存储在类型特征中,因此代码可以在一个地方查看此信息。这是std::iterator_traits
类型特征。
std::iterator_traits
处理任何内容,它来自std::iterator
模板以及任何类型的指针,无需任何调整。因此,通常最好使用std::iterator
作为基础,以避免编写自己的特征专业化。如果你不能这样做,仍然可以提供必要的特征,但它会更难。
标记类和迭代器类型:
C ++中有几种不同类型的迭代器,它们具有不同的行为,可以/不能做很多不同的事情。查看http://cplusplus.com/reference/std/iterator/以查看可用的迭代器类型以及它们可以执行的操作。这些图并不意味着以面向对象的方式(即input_iterator
既不是forward_iterator
的子类也不是基类,而是作为API类型的派生。即您可以使用为输入迭代器编写的所有算法以及前向迭代器。页面上的表格将告诉您必须为每个类别提供哪些方法。
由于这些类别实际上并不是彼此的子类(它们不应该是,特别是在从不同类型的集合中提交时),因此使用另一种机制来标识每个迭代器的功能。描述每个迭代器的std::iterator_traits
中还包含一个空标记类,它告诉迭代器可以做什么以及它不能做什么。如果您不编写自己的特征,则需要在实例化时将此标记类提供给std::iterator
模板。
示例:强>
此示例取自迭代器上的cplusplus.com部分:
class myiterator : public iterator<input_iterator_tag, int>
{
int* p;
public:
myiterator(int* x) :p(x) {}
myiterator(const myiterator& mit) : p(mit.p) {}
myiterator& operator++() {++p;return *this;}
myiterator operator++(int) {myiterator tmp(*this); operator++(); return tmp;}
bool operator==(const myiterator& rhs) {return p==rhs.p;}
bool operator!=(const myiterator& rhs) {return p!=rhs.p;}
int& operator*() {return *p;}
};
这个迭代器实际上没有意义,因为它只包装一个指针,它也可以直接使用。但它可以作为解释。迭代器通过提供适当的标记从std::iterator
派生为input_iterator
。此外,模板被告知,此迭代器正在迭代int
s。所需的所有其他类型difference_type
,reference
,poiner
等都会被模板自动输入。在某些情况下,手动更改其中某些类型可能是有意义的(例如,std::shared_ptr
有时必须用作pointer
。此迭代器所需的特征也将自动存在,因为它已经从std::iterator
派生而std::iterator_traits
知道在哪里可以找到所有必要的信息。
答案 1 :(得分:6)
class FileDB
{
class iterator;
public:
FileDB(std::string dir) : m_rootDir(dir) { m_files = getFileTreeList(); }
void loadFile(std::string filename, File &file) const;
void saveFile(std::string filename, const File &file) const;
iterator begin()
{
return iterator(m_files.begin(), *this);
}
iterator end()
{
return iterator(m_files.end(), *this);
}
private:
std::list<std::string> getFileTreeList() const;
private:
std::string m_rootDir;
std::list<std::string> m_files;
}
class FileDB::iterator
{
public:
iterator(std::list<std::string>::iterator pos, FileDB& owner) : m_pos(pos), m_owner(owner){}
bool operator==(const iterator& rhs) {return m_pos == rhs.m_pos;}
bool operator!=(const iterator& rhs) {return m_pos != rhs.m_pos;}
//etc
void operator++() {++m_pos;}
File operator*()
{
return openFile(*m_pos); // for example open some file descriptor
}
~iterator()
{
closeFile(*m_pos); // clean up!
}
private:
std::list<std::string>::iterator& m_pos;
FileDB& m_owner;
};
答案 2 :(得分:3)
这是迭代器,它在遍历期间计算子节点。 我是为windows编写的,但我认为将其用于其他平台并不困难。
#include <list>
#include <windows.h>
#include <assert.h>
#include <iostream>
#include <string>
class File{};
class Iterator
{
public:
virtual bool isDone() = 0;
virtual void next() = 0;
virtual std::string getFileName() = 0;
virtual ~Iterator(){};
};
bool operator== (Iterator& lhs, Iterator& rhs);
class EndIterator : public Iterator
{
public:
virtual bool isDone() {return true;}
virtual void next(){};
virtual std::string getFileName() {return "end";};
};
class DirectoryIterator : public Iterator
{
public:
DirectoryIterator(const std::string& path);
virtual bool isDone();
virtual void next();
virtual std::string getFileName();
virtual ~DirectoryIterator();
private:
std::list<Iterator*> getSubelementsList(const std::string& path) const;
void init();
private:
bool m_wasInit;
std::string m_path;
std::list<Iterator*> m_leaves;
std::list<Iterator*>::iterator m_current;
};
class FilesIterator : public Iterator
{
public:
FilesIterator(const std::string& fileName);
virtual bool isDone(){return true;};
virtual void next(){};
virtual std::string getFileName();
virtual ~FilesIterator(){};
private:
std::string m_fileName;
};
class DbItertor
{
public:
DbItertor(Iterator* iterator) : m_ptr(iterator){}
DbItertor(const DbItertor& rhs) {*m_ptr = *rhs.m_ptr;}
std::string operator*()
{
if(m_ptr->isDone())
return "end";
return m_ptr->getFileName();
}
//File operator->(){return FileOpen(m_ptr->getFileName());}
void operator++() {m_ptr->next();}
~DbItertor(){delete m_ptr;}
private:
Iterator* m_ptr;
};
class FileDB
{
public:
FileDB(std::string dir) : m_rootDir(dir){}
DbItertor begin()
{
return DbItertor(new DirectoryIterator(m_rootDir));
}
DbItertor end()
{
return DbItertor(new EndIterator());
}
private:
std::string m_rootDir;
};
FilesIterator::FilesIterator(const std::string& fileName) :
m_fileName(fileName)
{}
std::string FilesIterator::getFileName()
{
return m_fileName;
}
DirectoryIterator::DirectoryIterator(const std::string& path) :
m_wasInit(false),
m_path(path)
{}
void DirectoryIterator::init()
{
m_leaves = getSubelementsList(m_path);
m_current = m_leaves.begin();
m_wasInit = true;
next();
}
DirectoryIterator::~DirectoryIterator()
{
for(std::list<Iterator*>::iterator i = m_leaves.begin(); i != m_leaves.end(); ++i)
delete *i;
}
void DirectoryIterator::next()
{
if(!m_wasInit)
init();
if(isDone())
return;
if((*m_current)->isDone())
++m_current;
else
(*m_current)->next();
}
bool DirectoryIterator::isDone()
{
if(!m_wasInit)
init();
return (m_leaves.size() == 0) || (m_current == --m_leaves.end());
}
std::string DirectoryIterator::getFileName()
{
if(!m_wasInit)
init();
return (*m_current)->getFileName();
}
std::list<Iterator*> DirectoryIterator::getSubelementsList(const std::string& path) const
{
std::list<Iterator*> result;
WIN32_FIND_DATA fdFile;
HANDLE hFind = NULL;
char sPath[2048] = {0};
sprintf(sPath, "%s\\*.*", path.c_str());
hFind = FindFirstFile(sPath, &fdFile);
assert(hFind != INVALID_HANDLE_VALUE);
do
{
if(strcmp(fdFile.cFileName, ".") != 0 && strcmp(fdFile.cFileName, "..") != 0)
{
std::string fullName = path;
fullName += std::string(fdFile.cFileName);
if(fdFile.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)
{
fullName += "\\";
result.push_back(new DirectoryIterator(fullName));
}
else
{
result.push_back(new FilesIterator(fullName));
}
}
}
while(FindNextFile(hFind, &fdFile));
FindClose(hFind);
return result;
}
bool operator== (Iterator& lhs, Iterator& rhs)
{
return lhs.getFileName() == rhs.getFileName();
}
bool operator!= (DbItertor& lhs, DbItertor& rhs)
{
return *lhs != *rhs;
}
int main()
{
FileDB db("C:\\456\\");
for(DbItertor i = db.begin(); i != db.end(); ++i)
{
std::cout << *i << std::endl;
}
return 0;
}