我写了一个非常简单的文件管理数据库,基本上看起来像这样:
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;
}
Run Code Online (Sandbox Code Playgroud)
现在我想迭代遍历数据库中包含的所有文件,例如使用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
}
}
Run Code Online (Sandbox Code Playgroud)
我已经阅读了类似问题的答案,有些人建议推导std::iterator,有些人要使用std::iterator_traits,但我真的不明白该怎么做.尝试实现自定义迭代器时可能出现什么问题?什么是简单而优雅的方式呢?
编辑: 请不要考虑使用提升,我的问题更具概念性.
编辑2:
FileDB的工作方式如下:
ROOTDIR
foo2的
欢
barM中
所以基本上,我可以通过它的名字找到一个文件.
由于我的容器不在内存中,因此我没有指向其数据的指针.所以我的想法是将文件的路径存储在迭代器中.这样,我可以operator==使用字符串比较来实现,因为路径应该是唯一的.从中返回的迭代器fileDB.end()将是一个空字符串,operator*并将调用fileDB::loadFile()其文件路径.
我最担心的是operator++.有了文件名,我可以找到包含目录并搜索下一个文件,但这实际上是无效的.有关如何做到这一点的任何想法?或者我对整个概念完全错了?
LiK*_*Kao 12
自己编写迭代器几乎不可能.向类中添加迭代器的最简单方法是重用现有的迭代器.您应该知道,例如,指针和C++中的迭代器一样好,所以有很多方法可以提供迭代器而无需实际编写自己的迭代器.
这基本上是C++在很多方面的工作方式.它试图通过给图书馆作者带来很多负担,使最终用户的语言易于使用和简单化.即图书馆作家可以写出所有不合适的东西,所以最终用户不必这样做.迭代器通常是库的一部分.
话虽如此,实际上是丑陋的部分:
为了能够编写自己的迭代器,这里有一些你需要注意的事情.
类型特征:
类型特征是一种向C++中的类型添加附加信息的简单机制,即使对于无法自行更改的类型也是如此.例如,对于迭代器,重要的是要知道它迭代的内容(即包含的类型).获取给定迭代器的这些信息的方法很大程度上取决于迭代器.对于实际上是对象的迭代器,您可以在类中添加typedef并使用它们,但对于作为指针的迭代器,您需要从指针类型推断它.为了实现这一点,信息存储在类型特征中,因此代码可以在一个地方查看此信息.这是std::iterator_traits类型特征.
std::iterator_traits处理从std::iterator模板以及任何类型的指针派生的任何事物,无需任何调整.因此,通常最好将其std::iterator作为基础,以避免编写自己的特征专业化.如果你不能这样做,它仍然可以提供必要的特征,但它会更难.
标记类和迭代器类型:
C++中有几种不同类型的迭代器,它们具有不同的行为,可以/不能做很多不同的事情.请查看http://cplusplus.com/reference/std/iterator/,了解可用的迭代器类型以及它们可以执行的操作.这些图并不意味着以面向对象的方式(即,input_iterator既不是a的子类也不是a的基类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;}
};
Run Code Online (Sandbox Code Playgroud)
这个迭代器实际上没有意义,因为它只包装一个指针,它也可以直接使用.但它可以作为解释.迭代源自std::iterator作为input_iterator通过提供合适的标签.此外,模板被告知,这个迭代器正在迭代int.所有其他类型,需要哪些difference_type,reference,poiner等会自动由模板infered.在某些情况下,手动更改其中一些类型可能是有意义的(例如std::shared_ptr,pointer有时必须使用它).此迭代器所需的特征也将自动存在,因为它已经派生自std::iterator并且std::iterator_traits知道在哪里可以找到所有必要的信息.
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;
};
Run Code Online (Sandbox Code Playgroud)