2009-12-02 60 views
0

我有一个名为Particle的类,它有一个std :: set作为成员。这个类看起来是这样的:如何正确封装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(); 
}; 

我很不满getFirstCell()getNextCell()尤其是hasNextCell(),他们的存在是因为我不想暴露自己设定。我不得不使用通过++ipc--ipc的方式,因为if((ipc+1) == this->cells.end())给出了一个编译器错误,ipc + 1似乎是问题所在。

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

在此先感谢。

编辑:我发布的代码只是类结构的一个例子。 “真实”类包含更多的集合和其他数据,这对于这个问题并不重要(我认为)。

+0

您可以将'hasNextCell'实现为'iterator i = ipc; return ++ i!= cells.end();'以避免在查询期间改变状态。就个人而言,我会与詹姆斯的回答一起,并展示“开始”和“结束”。 – 2009-12-02 18:04:19

回答

4

我不确定你为什么不想公开这个集合本身,但是如果它是因为你想确保这个集合的内容不能在class Particle之外被修改,那么只需要返回const这个迭代器就可以让这个集合“读取仅 - “,例如

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

谢谢,我会试试这个。不幸的是,你是第二个提出这个建议的人,所以只有一个赞成:) – DaClown 2009-12-02 17:06:42

+0

没问题,只要你注意到使用常量迭代器而不是正常的迭代器来解决你的具体问题;) – catchmeifyoutry 2009-12-02 17:13:13

+1

'begin()'和'end ()'如果从类上下文中清楚了什么是迭代器所表示的,则更加通俗。 – 2009-12-02 17:13:41

0

你显示的内容除了三个获得者之外什么也没有做。通过使这些getters成为Particle类的一部分的操作来封装集合,那么根本就不需要getter:封装。

+0

我在我的问题中提出的代码不完整,其中有更多的集合。最后只有一个数据容器,它包含单元格,点,标量索引......并且有很多这些粒子存储在向量中。因此a不能将处理函数封装在粒子中。 – DaClown 2009-12-02 17:04:00

4

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

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

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

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

+0

+1为“获取”功能设置状态注释。 – wheaties 2009-12-02 17:04:11

+1

其中一个“暴露容器”问题(首先我发现,诚实;):http://stackoverflow.com/questions/1484052/should-i-expose-iterators-and-adaptor-methods-or-a-whole- container-in-c – 2009-12-02 17:06:57

+0

我会随着暴露的开始和结束。但我不知道名为getNext ...的函数在语义上是不是可以改变内部状态的。下一个函数还会如何到达下一个元素。顺便说一句,我借用Java迭代器的这种语法,因为我没有想法。 – DaClown 2009-12-02 17:11:57

0

如果你想留住你已经有了整体实施,但只是消除getFirstCell(),你可以在构造函数中初始化IPC。如上所述,明智地使用const并明确区分访问者和变异者将阐明界面。另外,如果你想在你的类上实现迭代器,那么我会建议addcell()返回一个引用新单元格的迭代器,并在遇到错误时抛出异常。

+0

感谢您的回复。 addCell不返回引用,因为这里使用集合的全部要点是其中元素的唯一性。我只需添加一个单元格并检查该集合的大小是否已更改,以确定是否将某个元素添加到集合中。但是,当然,再加上另一个回复以及通过开始和结束函数替换getter,如果它已经存在,我可以返回迭代器和set.end()。我会研究这个。 – DaClown 2009-12-02 17:23:07

2

为了防止暴露组:迭代(不承诺用户超过需要的话),你可以创建一个包装:

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的算法。

也助推:: iterator_facade可以在这里有用...

1

的问题是,你要在这里完成真正的东西。现在,你的班级似乎(至少对我来说)做的不仅仅是好事 - 它使得集合中的内容变得更加困难而不是简单。

我会看看Particle,并确定它是否可以提供一些有意义的方法来存储/访问一堆单元格。如果它真的只是一个简单的容器,那么你可以使用像typedef std::set<cell> Particle;这样的更好的方法,所以最终用户可以像使用其他任何方法一样在这个集合上使用算法等。我只写一个类来封装,如果你真的可以封装一些有意义的东西 - 也就是说,如果你的类可以体现一些有关粒子的“知识”,所以其他代码可以将粒子用作本身有意义的东西。

现在,你的Particle不过是一个容器 - 它看起来也不是一个特别好的容器。除非你真的可以添加一些东西,否则只需使用已有的东西就可以更好。

+0

是的,它是其他数据中提到的数据结构。我将来的文章将包括我所有的代码,将它切割为必需品似乎更容易混淆,然后提高可读性。 – DaClown 2009-12-02 17:41:01