【问题标题】:How to realize a custom implementation of a std-like iterator?如何实现类标准迭代器的自定义实现?
【发布时间】:2012-03-30 14:57:32
【问题描述】:

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

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 的工作方式如下:

  • 根目录

    • 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++。有了文件名,我就可以找到包含目录并搜索下一个文件,但这确实无效。关于如何做到这一点的任何想法?还是我的整个概念完全错了?

【问题讨论】:

  • 最简单的方法大概是使用iterator_facade from Boost.Iterator.
  • 更简单的方法是使用 boost::filesystem 并避免编写任何代码...
  • 好的,我应该知道我应该添加信息,我不想使用 boost ;) 主要是因为我想了解迭代器是如何工作的。
  • Iterator 在概念上很简单,但充满了烦人的实现细节 Boost.Iterator 照顾。不要重新发明轮子。

标签: c++ iterator std


【解决方案1】:

自己编写迭代器几乎不是很漂亮。将迭代器添加到类中的最漂亮的方法是重用现有的。您应该知道,例如,指针与 C++ 中的迭代器一样好,因此有很多方法可以提供迭代器,而无需自己编写。

这基本上是 C++ 在许多方面的工作方式。它试图通过给库编写者增加很多负担,使最终用户的语言变得简单易用。 IE。库作者可以编写所有不漂亮的东西,因此最终用户不必这样做。迭代器通常是库的一部分。

话虽如此,真正丑陋的部分来了:

为了能够编写自己的迭代器,您需要注意以下几点。

类型特征:

类型特征是一种向 C++ 中的类型添加附加信息的简单机制,它甚至适用于自身无法更改的类型。例如,对于迭代器,重要的是要知道它迭代的是什么(即包含的类型)。获取给定迭代器的这些信息的方式很大程度上取决于迭代器。对于实际上是对象的迭代器,您可以在类中添加 typedef 并使用它们,但对于作为指针的迭代器,您需要从指针类型推断它。为了使这成为可能,信息被存储在类型特征中,因此代码可以在一个地方查看此信息。这是std::iterator_traits 类型特征。

std::iterator_traits 可以处理任何来自std::iterator 模板以及任何类型的指针的东西,无需任何调整。所以通常最好使用std::iterator 作为基础,以避免编写自己的特质专业。如果您无法做到这一点,仍然可以提供必要的特征,但会更难。

标记类和迭代器类型:

C++ 中有几种不同类型的迭代器,它们具有不同的行为,可以/不能做很多不同的事情。查看http://cplusplus.com/reference/std/iterator/ 以了解可用的迭代器类型以及它们可以做什么。这些图并不意味着以面向对象的方式(即input_iterator 既不是forward_iterator 的子类也不是基类),而是作为一种API 派生。 IE。您可以使用为输入迭代器编写的所有算法,也可以使用前向迭代器。页面上的表格将告诉您必须为每个类别提供哪些方法。

由于这些类别实际上并不是彼此的子类(它们不应该是,尤其是来自不同类型的集合时),因此使用另一种机制来识别每个迭代器的功能。在描述每个迭代器的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。模板也被告知,这个迭代器正在迭代ints。所有其他需要的类型difference_typereferencepoiner 等都由模板自动推断。在某些情况下,手动更改其中一些类型可能是有意义的(例如,std::shared_ptr 有时必须用作pointer)。此迭代器所需的特征也将自动存在,因为它已经派生自 std::iterator 并且 std::iterator_traits 知道在哪里可以找到所有必要的信息。

【讨论】:

    【解决方案2】:
    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;
    };
    

    【讨论】:

    • 这看起来很有希望!除了我的数据库可能必须存储数百万个文件这一事实之外,这会使启动非常缓慢并且 FileDB 会消耗相当多的内存。
    • 是的,从std::iterator 派生是个好主意。没有虚拟析构函数是没有问题的。仅当您需要多态行为作为基本类型和任何类型的数据管理时,才需要虚拟析构函数。迭代器应该/都不需要。此外,您的迭代器与所有标准算法不兼容,因为它不提供必要的 typedef 或特征。最后有一个很好的规则:如果有疑问,请按照 STL 进行。看看bits/stl_iterator.h。所有 STL 迭代器都派生自 std::iterator
    • @LiKao:我不能修改这段代码,通过添加iterator_traits使其与标准算法兼容吗?
    • @Ben,是的,你可以。支持类似 stl 的界面(和行为)足以与 stl 算法兼容。
    • @LiKao:同意,修改答案。
    【解决方案3】:

    这是在遍历期间计算子节点的迭代器。 我是为windows写的,不过我觉得给其他平台做castomize也不难。

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

    【讨论】:

      猜你喜欢
      • 2018-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-22
      • 2013-12-10
      • 2014-05-23
      • 2018-08-25
      • 2015-05-26
      相关资源
      最近更新 更多