【问题标题】:How to encapsulate a std::set properly?如何正确封装 std::set?
【发布时间】:2009-12-02 16:47:34
【问题描述】:

我有一个名为 Particle 的类,它有一个 std::set 作为成员。该类如下所示:

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

我对@9​​87654322@、getNextCell() 尤其是hasNextCell() 非常不满意,它们的存在是因为我不想暴露集合本身。我不得不使用通过++ipc--ipc 的方式,因为if((ipc+1) == this-&gt;cells.end()) 给出了编译器错误,ipc+1 似乎是问题所在。

封装一个集合并访问它的好方法是什么?另外,有没有摆脱getFirstCell()函数的好方法?

提前致谢。

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

【问题讨论】:

  • 您可以将hasNextCell 实现为iterator i=ipc; return ++i != cells.end(); 以避免在查询期间更改状态。就个人而言,我会接受詹姆斯的回答并公开beginend

标签: c++ iterator encapsulation


【解决方案1】:

我不确定你为什么不想暴露集合本身,但如果是因为你想确保集合的内容不能在 class Particle 之外更改,只需返回 const 迭代器,这使得设置为“只读”,例如

typedef std::set<vtkIdType>::const_iterator CellIterator;
CellIterator beginCell() const { return this->cells.begin(); }
CellIterator endCell() const { return this->cells.end(); }

【讨论】:

  • 谢谢,我试试这个。不幸的是,你是第二个提出这个建议的人,因此只有一个赞成票:)
  • 没问题,只要您注意到针对您的特定问题使用 const 迭代器而不是普通迭代器 ;)
  • begin()end() 如果从类上下文中清楚地知道迭代器代表什么,则更为惯用。
  • 当然,除非您将向该类添加更多集合和迭代器,正如 DaClown 在此线程的另一条评论中提到的那样。
  • 技术上,没有。但在那种情况下,我实际上会像 gf 那样主张一种惯用的方法,因为以后可能会不清楚Particle::iteratorconst。所以我建议typedef std::set&lt;vtkIdType&gt;::const_iterator const_iterator; 避免以后混淆。当然,随着返回的迭代器越多,Particle::iterator 的类型可能会变得更加混乱。
【解决方案2】:

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

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

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

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

【讨论】:

  • “暴露容器”问题之一(我第一次发现,老实说;):stackoverflow.com/questions/1484052/…
  • 我将使用暴露的开头和结尾。但我不确定如何在语义上不允许名为 getNext... 的函数更改内部状态。下一个函数将如何到达下一个元素。顺便说一句,我从 Java 迭代器中借用了这种语法,因为我没有想法。
【解决方案3】:

为了防止暴露 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());
}

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

boost::iterator_facade 在这里也可能有用...

【讨论】:

    【解决方案4】:

    问题实际上是您要在这里完成的工作。现在,你的课程似乎(至少在我看来)弊大于利——它使处理集合内容变得更加困难而不是更容易。

    我会看看 Particle,并弄清楚它是否可以提供一些有意义的东西,而不是某种存储/访问一堆单元格的方式。如果它真的只是一个简单的容器,那么使用typedef std::set&lt;cell&gt; Particle; 之类的东西会更好,因此最终用户可以像使用其他任何东西一样在这个集合上使用算法等。如果你真的可以封装一些有意义的东西,我只会写一个类来封装它——即,如果你的 Particle 类可以包含一些关于粒子的“知识”,那么其他代码可以将粒子作为本身有意义的东西来工作。

    现在,您的Particle 只是一个容器——而且它看起来也不是一个特别好的容器。除非你真的可以添加一些东西,否则你最好只使用已经存在的东西。

    【讨论】:

    • 是的,它是一个数据结构,里面有数据,就像其他cmet中提到的那样。在以后的帖子中,我将包含我的所有代码,将其缩减为基本内容似乎更容易混淆,然后增加可读性。
    【解决方案5】:

    除了三个吸气剂之外,您显示的内容没有任何作用。通过将使用这些 getter 的操作作为 Particle 类的一部分来封装该集合,那么您根本不需要这些 getter:瞧,封装了。

    【讨论】:

    • 我在问题中输入的代码不完整,里面还有更多集合。最后只是一个数据容器,它包含单元格、点、标量的索引......并且有很多这些粒子存储在一个向量中。因此a不能将处理函数封装在粒子中。
    【解决方案6】:

    如果您想保留已有的通用实现,但只需消除 getFirstCell(),您可以在构造函数中初始化 ipc。如上所述,明智地使用const 并明确区分访问器和修改器将澄清接口。此外,如果您要在您的类上实现迭代器,那么我建议addcell() 返回一个引用新单元格的迭代器,并在遇到错误时抛出异常。

    【讨论】:

    • 感谢您的回复。 addCell 不返回引用,因为在这里使用集合的重点是其中元素的唯一性。我只需添加一个单元格并检查集合的更改大小以确定是否将元素添加到集合中。但是,当然,结合另一个回复和用 begin 和 end 函数替换 getter,如果它已经存在,我可以返回一个迭代器和 set.end()。我会调查一下。
    猜你喜欢
    • 2013-09-12
    • 1970-01-01
    • 1970-01-01
    • 2014-04-17
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-05
    相关资源
    最近更新 更多