【问题标题】:C++ iterator "remove-proof"C++ 迭代器“防移除”
【发布时间】:2012-10-10 16:48:14
【问题描述】:

我不知道是否有我需要的特殊关键字。我正在写一个基本的observer pattern,我担心一些问题。我的实现是经典的。我正在使用一个 std::set 观察者,每当我需要触发一个事件时,我都会遍历这个集合并调用每个观察者的 notify 方法。我的问题如下。在以下情况下,当可观察对象向观察者发送事件时会发生什么:

  • 一个观察者想要在事件期间将自己(或任何其他观察者)从观察者集中移除?
  • 一个观察者想要清除观察者集(移除所有观察者)?
  • 一个观察者破坏了可观察对象?

我知道所有这些情况最终都会发生。我对第三个有想法,但这是题外话。对于第一种和第二种情况,问题在于删除或清除 std::set 将使我用来枚举可观察对象的迭代器无效。即使没有,observable 也不应该通知任何在事件处理期间将被删除的观察者。

我还没有找到一个 set 的实现,它提供了一个能够在删除任何项目时保持有效的迭代器。不过,有可能实现它,但代价是一些间接并在容器中存储对活动迭代器的任何引用,以便在必要时对其进行更新。

另一种解决方案是复制观察者集合,迭代副本并检查当前迭代的观察者是否仍在真实集合中。 (这会忘记在活动期间添加的任何新观察者,但这种情况我不在乎)

您对这个问题有什么建议/解决方案吗?

【问题讨论】:

  • 您可以让观察者将自己标记为“todelete”,并让迭代代码在遇到如此标记的观察者时进行实际删除,而不是让观察者删除自己。
  • @StevenBurnap 这带来了一个语义问题:删除是立即生效还是在观察结束时生效?例如,如果 Observer4 删除了 Observer6,Observer6 是否仍会收到当前事件的通知?
  • 啊...我没有仔细阅读并假设观察者只会删除自己。
  • @Robᵩ:好点,但通常的观察者模式不会以任何特定顺序通知观察者(事实上他使用的是set而不是像vector这样的插入顺序容器表明这也是这里的意图),这意味着处理删除的唯一可靠方法是在通知所有观察者之后。
  • 我的问题是当观察者删除 observable 时。如果一个观察者删除了另一个观察者,我假设被删除的观察者有责任在他的破坏期间将自己从集合中移除。这又回到了第一个问题:当一个观察者从活跃的观察者集中移除另一个观察者时该怎么办?

标签: c++ iterator observer-pattern


【解决方案1】:

我将假设您在容器中存储指针或其他东西,而不是实际上是观察者对象本身。因为你显然不能让观察者代码删除观察者对象!

std::set 在迭代器处于活动状态时需要修改集合时根本无法使用。这不是它的用途。

如果您需要从容器中移除东西,您可以尝试从事件例程中返回一个值,该值告诉调用者(具有迭代器的代码)从容器中移除该观察者。该代码可以删除迭代器指向的东西并沿着序列正确继续。

如果您需要添加东西,请不要将它们直接添加到容器中。相反,将它们添加到队列中,并让触发事件的代码在完成对容器的迭代后添加新内容。

如果容器有少量对象,并且对象很小(指针),我可能只是复制容器并遍历副本。这样,观察者可以随心所欲地处理容器,而不会搞砸迭代器。

【讨论】:

  • 实际上,std::set<> 对于这个用例有很好的迭代器失效语义。唯一的问题情况是当您有一个迭代器指向要删除的项目时。 OP的问题是这个迭代器是一个局部变量,并且调用了另一个函数来移除观察者。
【解决方案2】:

让观察者的通知方法接受一个指向该观察者的迭代器,并返回一个指向下一个观察者的迭代器:

// PSEUDO-CODE not to be taken literally.

class normalObserver : public Observer {
  iterator notify(iterator me) { 
    assert(*me == this); 
    // do stuff
    return ++me;
  }
 };

 class deleteMeObserver : public Observer {
   iterator notify(iterator me) {
    assert(*me == this); 
     // do stuff
     object.observers.erase(me++);
     return me;
   }
 };

 class deleteEveryObserver :public Observer {
   iterator notifiy(iterator me) {
    assert(*me == this); 
     // do stuff
     object.observers.clear();
     return object.observers.end();
   }
};

 class Object {
   set::set<Observer*> observers;
   void notifyObservers() {
     for(it = observers.begin(); it != observers.end(); ) {
       it = (*it)->notify(it);
     }
   }
 };

【讨论】:

  • deleteMeObserver::notify() 中,it 是否应该改为me
  • 并且erase() 不返回迭代器...已修复。
  • 这可行,但对观察者不友好。我没有提到就我而言,观察者是我正在编写的图书馆的用户。因此,我对这个解决方案不太满意。
  • 然后我会按照其他地方的建议让观察者返回一些东西给带有下一步做什么的说明的对象:enum NotificationResult { Normal, DeleteMe, DeleteSomeOtherGuy, DeleteEveryone }
【解决方案3】:

我建议使用std::list 而不是set,因为这样在删除元素时迭代器不会失效,除非它当然会自行删除。

您可以通过将指针(最好是智能指针,甚至是弱指针)指向观察者对象而不是迭代器来处理想要移除自身的观察者(这样当它移除自己时,指针不会失效(只要它很聪明,也不会删除自己))

【讨论】:

  • 我相信 list 和 set 如果您在迭代容器时删除对象 也会有同样的问题。你持有的迭代器可能会失效,然后你就迷路了。
  • 实际上,std::set&lt;&gt; 对于这个用例有很好的迭代器失效语义。唯一的问题情况是当您有一个迭代器指向要删除的项目时。 OP的问题是这个迭代器是一个局部变量,并且调用了另一个函数来移除观察者。
  • @BoPersson list 擅长在迭代容器时删除对象,只要您小心将 list::erase 的返回值分配为迭代器的下一个值(不使用 ++it即不使用已删除的迭代器)
  • @AndréCaron 然后你可以使用智能指针(正如我提到的)。迭代器在技术上就像一个指针。或者 Rob 或 Michael 的解决方案都很好。
  • @tozka - 如果列表(或集合)包含指向函数的指针,并且这些函数可以从容器中删除元素,则会出现问题。我相信这就是 OP 正在尝试的。
【解决方案4】:

不要让观察者直接访问集合。让他们通过可以访问集合和迭代器的方法进行修改,在调用观察者之前应该递增。此方法可以检查迭代器处的项目是否正在被删除,如果是则增加它。对于全部删除的情况,只需将迭代器设置为end()

【讨论】:

  • 我实际上并没有授予对集合的访问权限。我只提供了三种方法:addObserver、removeObserver 和 clearObservers。您的解决方案需要将迭代器存储在可观察的 insteaf 中,而不是将其存储在堆栈中。我想不会太麻烦。
猜你喜欢
  • 2012-04-19
  • 2015-08-09
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多