如何实现类似std的迭代器的自定义实现?

时间:2012-03-30 14:57:32

标签: c++ iterator std

我编写了一个非常简单的文件管理数据库,基本上看起来像这样:

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

    • foo1
      • BAR1
        • foo1bar1_1.txt
        • foo1bar1_2.txt
      • BAR2
        • foo1bar2_1.txt
        • foo1bar2_2.txt
    • foo2的

    • FOON

      • barM中

        • fooNBarM_x.txt

所以基本上,我可以找到一个名字的文件。

由于我的容器不在内存中,因此我没有指向其数据的指针。所以我的想法是将文件的路径存储在迭代器中。这样,我可以使用字符串比较来实现operator==,因为路径应该是唯一的。从fileDB.end()返回的迭代器将是一个空字符串,operator*将使用其文件路径调用fileDB::loadFile()

我最关心的是operator++。有了文件名,我可以找到包含目录并搜索下一个文件,但这实际上是无效的。有关如何做到这一点的任何想法?或者我对整个概念完全错了?

3 个答案:

答案 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_typereferencepoiner等都会被模板自动输入。在某些情况下,手动更改其中某些类型可能是有意义的(例如,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;
}