代码之家  ›  专栏  ›  技术社区  ›  DaClown

如何正确封装std::set?

  •  0
  • DaClown  · 技术社区  · 15 年前

    我有一个名为particle的类,它的std::设置为成员。类如下所示:

    class Particle {
    private:
        std::set<vtkIdType> cells;
        std::set<vtkIdType>::iterator ipc;
    
    public:
    
        Particle() {};
    
        enum state {EXISTS = -1, SUCCESS = 0, ERROR = 1};
    
        state addCell(const vtkIdType cell);
    
        int numCells() { return static_cast<int>(cells.size()); }
    
        vtkIdType getFirstCell() { return (*(ipc = this->cells.begin()));}
        vtkIdType getNextCell() { return *(++ipc); }
        vtkIdType hasNextCell() { ++ipc; if (ipc == this->cells.end()) return false; --ipc; return true; }
    
        std::string getOutput();
    };
    

    我很不满意 getFirstCell() , getNextCell() 特别是 hasNextCell() 它们的存在是因为我不想公开集合本身。我得走这条路 ++ipc --ipc 因为 if((ipc+1) == this->cells.end()) 给出一个编译器错误,ipc+1似乎是问题所在。

    封装并访问集合的好方法是什么?还有,有没有一个很好的方法来摆脱 GETFILSTCELL() 功能?

    事先谢谢。

    编辑:我发布的代码只是类结构的一个例子。“real”类包含更多的集合和其他对这个问题不那么重要的数据(我假设)。

    6 回复  |  直到 15 年前
        1
  •  4
  •   catchmeifyoutry    15 年前

    我不知道您为什么不想公开集合本身,但如果是因为您想确保集合的内容不能在外部进行更改 class Particle 就回来吧 const 使集合“只读”的迭代器,例如

    typedef std::set<vtkIdType>::const_iterator CellIterator;
    CellIterator beginCell() const { return this->cells.begin(); }
    CellIterator endCell() const { return this->cells.end(); }
    
        2
  •  4
  •   James McNellis    15 年前

    原因在于 ipc+1 不起作用是吗 std::set 只支持双向迭代器,它支持 operator++ operator-- ;以便使用 operator+ ,您需要使用随机访问迭代器。

    我在您的设计中看到的一个问题是,您的函数的命名类似于访问器(getsuchandsu),但它们也修改了对象的内部状态。( ipc 被修改)。这可能会导致混乱。

    您可以尝试使用一些返回迭代器的成员函数(a begin end 例如),并允许类的用户使用迭代器访问内部集,同时仍然封装集实现。

    您可以返回集合的迭代器类型,或者如果您想要更多的控制或封装,您可以实现自己的迭代器类来包装集合的迭代器。

        3
  •  2
  •   maxim1000    15 年前

    要防止公开set::iterator(不向用户承诺超出需要的内容),可以创建包装:

    class Particle::iterator
    {
    public:
      iterator()
      {}
      iterator &operator++()
      {
        ++InternalIterator;
        return *this;
      }
      vtkIdType &operator*() const
      {
        return *InternalIterator;
      }
      ...//other functionality required by your iterator's contract in the same way
    private:
      iterator(const std::set<vtkIdType> &internalIterator)
        :InternalIterator(internalIterator)
      {}
      std::set<vtkIdType>::iterator InternalIterator;
    };
    
    Particle::iterator Particle::GetBeginCell()
    {
      return iterator(cells.begin());
    }
    Particle::iterator Particle::GetEndCell()
    {
      return iterator(cells.end());
    }
    

    因此,您将摆脱内部迭代器(因为只有一个迭代器是非常有限的),并且能够在粒子迭代器上使用STL的算法。

    另外,Boost::Iterator_Facade在这里可能很有用…

        4
  •  1
  •   Jerry Coffin    15 年前

    问题是你到底想在这里完成什么。现在,你的课似乎(至少对我来说)弊大于利——这使得处理集合内容变得更困难而不是更容易。

    我会研究粒子,并找出它是否能提供一些除了存储/访问一堆细胞之外有意义的东西。如果它真的只是一个简单的容器,那么你会更好地使用 typedef std::set<cell> Particle; 因此,最终用户可以像使用其他算法一样,在这个集合上使用算法等。我只会编写一个类来封装,如果您真的能够封装一些有意义的东西——例如,如果 Particle 类可以包含一些关于粒子的“知识”,因此其他代码可以将粒子作为一个本身有意义的东西来使用。

    现在,你的 微粒 只不过是一个容器——它看起来也不像一个特别好的容器。除非你真的能添加一些东西,否则你最好只使用已经存在的东西。

        5
  •  0
  •   just somebody    15 年前

    你的表演除了三个盖茨特之外什么都不做。通过使使用这些getter的操作成为粒子类的一部分来封装集合,那么您根本不需要getter:voila,封装的。

        6
  •  0
  •   Adam    15 年前

    getFirstCell() ,可以在构造函数中初始化IPC。如上所述,明智地使用 const 访问器和变异器之间的清晰区分将澄清接口。另外,如果要在类上实现迭代器,那么我建议 addcell() 返回引用新单元格的迭代器,并在遇到错误时引发异常。