Как реализовать пользовательскую реализацию стандартного итератора? - PullRequest
8 голосов
/ 30 марта 2012

Я написал очень простую базу данных для управления файлами, которая выглядит примерно так:

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, но я не совсем понимаю, как это сделать. Что может пойти не так при попытке реализовать пользовательский итератор? И какой простой, но элегантный способ сделать это?

EDIT: Пожалуйста, не рассматривайте возможность повышения, мой вопрос носит более концептуальный характер.

РЕДАКТИРОВАТЬ 2:

FileDB работает так:

  • ROOTDIR

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

    • Фун

      • закваска

        • fooNBarM_x.txt

Итак, я могу найти файл по имени.

Поскольку мой контейнер не находится в памяти, у меня нет указателей на его данные. Поэтому моя идея заключалась в том, чтобы сохранить путь к файлу в итераторе. Таким образом, я могу реализовать operator== со сравнением строк, так как пути должны быть уникальными. Итератор, возвращаемый из fileDB.end(), будет пустой строкой, а operator* вызовет fileDB::loadFile() со своим filepath.

Больше всего меня беспокоит operator++. Имея имя файла, я могу найти содержащий каталог и искать следующий файл, но это действительно неэффективно. Есть идеи, как это сделать? Или я совершенно не прав со своей концепцией?

Ответы [ 3 ]

12 голосов
/ 30 марта 2012

Писать итераторы самостоятельно вряд ли когда-нибудь красиво.Самый лучший способ добавить итератор в ваши классы - это повторно использовать существующий.Вы должны знать, что указатели, например, так же хороши, как итераторы в C ++, поэтому есть много способов предоставить итератор, фактически не создавая свой собственный.

По сути, C ++ работает во многих отношениях.Он пытается сделать язык доступным и простым для конечных пользователей, возлагая большую нагрузку на авторов библиотек.Т.е. авторы библиотек могут писать все несущественные вещи, поэтому конечному пользователю это не нужно.Итераторы, как правило, являются частью библиотеки.

Сказав это, вот собственно уродливая часть:

Чтобы иметь возможность писать свои собственные итераторы, вот некоторые вещи, которые вы должны знатьof.

Черты типа:

Черты типа - это простой механизм добавления дополнительной информации к типам в C ++, который работает даже с типами, которые нельзя изменить самим.Например, для итератора важно знать, что он повторяет (т. Е. Содержащийся тип).Способ получения этой информации для данного итератора во многом зависит от итератора.Для итераторов, которые на самом деле являются объектами, вы можете добавить typedefs в класс и использовать их, но для итераторов, которые являются указателями, вам нужно вывести его из типа указателя.Чтобы сделать это возможным, информация хранится в типовой характеристике, поэтому существует единственное место, в котором код может просматривать эту информацию.Это черта типа 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 с.Все другие типы, которые необходимы difference_type, reference, poiner и т. Д., Автоматически выводятся шаблоном.В некоторых случаях может иметь смысл изменить некоторые из этих типов вручную (например, std::shared_ptr должен иногда использоваться как pointer).Также черты, необходимые для этого итератора, будут существовать автоматически, поскольку он уже получен из std::iterator и std::iterator_traits знает, где найти всю необходимую информацию.

6 голосов
/ 30 марта 2012
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;
};
3 голосов
/ 31 марта 2012

Вот итератор, который вычисляет подузлы во время обхода.Я написал это для 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;
}
...