How to implement a custom implementation of a std-like iterator?

I wrote a very simple file management database that basically looks like this:

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; } 

Now I would like to iterate over all the files contained in the database, for example using 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 } } 

I read answers to similar questions, some of them were supposed to get std::iterator , some of them used std::iterator_traits , but I really don't understand how to do this. What could be wrong when trying to implement a custom iterator? And what a simple but elegant way to do this?

EDIT: Please do not consider using boost, my question is more conceptual.

EDIT 2:

FileDB works as follows:

  • Roottir

    • foo1
      • bar1
        • foo1bar1_1.txt
        • foo1bar1_2.txt
      • bar2
        • foo1bar2_1.txt
        • foo1bar2_2.txt
    • foo2

    • Foon

      • Yeast

        • fooNBarM_x.txt

Basically, I can find a file by its name.

Since my container is not in memory, I have no pointers to its data. So my idea was to save the file path in an iterator. This way I can implement operator== using string comparison, since the paths must be unique. The iterator returned from fileDB.end() will be an empty string, and operator* will call fileDB::loadFile() with its file path.

My biggest concern is operator++ . Having the file name, I can find out the directory contained and search for the next file, but it is really inefficient. Any ideas on how to do this? Or am I completely mistaken in my concept?

+7
source share
3 answers
 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; }; 
+6
source

Writing iterators yourself is hardly ever beautiful. The most preliminary way to add an iterator to your classes is to reuse an existing one. You should know that pointers, for example, are just as good as C ++ iterators, so there are many ways to provide an iterator without having to write your own.

This is basically how C ++ works in different ways. He is trying to make the language accessible and easy for end users, putting a lot of work on library writers. That is, library writers can write all unpretentious things, so end users do not need this. Iterators are usually part of the library.

Having said that, here comes the real ugly part:

To be able to write your own iterators, here are some things you need to know about.

Typical Features:

Typical traits are a simple mechanism for adding additional information to types in C ++, which works even with types that cannot be changed themselves. For example, it is important for an iterator to know that it is iterating (i.e. contained). The way this information is obtained for a given iterator is largely dependent on the iterator. For iterators that are actually objects, you can add typedefs to the class and use them, but for iterators that are pointers, you need to deduce them from the pointer type. To make this possible, information is stored in a type type, so there is a place where the code can view this information. This is a sign of the type std::iterator_traits .

std::iterator_traits works on anything that comes from the std::iterator template, as well as any pointer without any settings. Therefore, it is often better to use std::iterator as the basis, so as not to write a specialization. If you cannot do this, it is still possible to provide the necessary traits, but it will be more difficult.

Tag classes and iterator types:

There are several different types of iterators available in C ++ that have different behaviors and may / may not do many different things. Take a look at http://cplusplus.com/reference/std/iterator/ to find out which iterators are available and what they can do. Diagrams are not intended for an object-oriented approach (i.e. input_iterator is neither a sub nor a base class of a forward_iterator ), but rather as an API output type. That is, you can use all the algorithms that were written for the input-iterator, also using the forwarding iterator. In the table on the page you will find out what methods you need to provide for each category.

Since these categories are not actually subclasses of each other (they should not be, especially when sending from different types of collections), a different mechanism is used to determine the capabilities of each iterator. An empty tag class is also included in std::iterator_traits , which describes each iterator that reports what this iterator can do and what it cannot do. If you are not writing your own traits, you need to provide this tag class to the std::iterator template after creating the instance.

Example:

This example is taken from the cplusplus.com section on iterators:

 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;} }; 

This iterator doesn't really make sense, since it only wraps a pointer, which can also be used directly. However, this can serve as an explanation. The std::iterator is input_iterator from std::iterator as input_iterator by supplying the appropriate tag. It is also reported that this iterator iterates over int s. All other types that are required by difference_type , reference , poiner , etc., are automatically output by the template. In some cases, it may make sense to change some of these types manually (for example, a std::shared_ptr sometimes used as a pointer ). In addition, the traits necessary for this iterator will exist automatically, since it is already obtained from std::iterator , and std::iterator_traits knows where to find all the necessary information.

+11
source

Here is an iterator that computes subnodes during a traversal. I wrote this for windows, but I think it is not difficult to do for other platforms.

 #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; } 
+3
source

All Articles