我正在C ++应用程序中实现观察者模式。到目前为止,我有一个定义如下的基类:
template<typename CallbackType,
typename LockType = utils::Lock<boost::mutex>,
typename StorageType = std::vector<std::shared_ptr<CallbackType>>>
class CallbackManager
{
LockType mCallbackLock { "commons::CallbackManager", __FILE__, __LINE__ };
StorageType mCallbacks;
virtual uint64_t registerCallback( const std::shared_ptr<CallbackType>& aCallbackPtr );
virtual bool unregisterCallback( uint64_t aCallbackId );
void iterateSubscribers(const std::function<void(typename StorageType::value_type&)>&);
};
其中一项关键要求是能够(递归地)在registerCallback
上下文内执行unregisterCallback
/ iterateSubscribers
调用。
iterateSubscribers
函数以每个回调作为参数执行aFnc
的迭代调用。 aFnc
可能执行registerCallback
/ unregisterCallback
呼叫。当前实现如下:
void iterateSubscribers(const std::function<void(typename StorageType::value_type&)>& aFnc)
{
decltype(mCallbacks) callbacks;
{
// BEGIN SCOPED LOCK
auto lock = mCallbackLock.getScopedLock();
boost::ignore_unused( lock ); // To supress the warning in case NoLock is used
callbacks = mCallbacks;
// END SCOPED LOCK
}
for( auto it = callbacks.begin(); it != callbacks.end(); ++it )
{
aFnc( *it );
}
}
我正在执行回调容器的副本,从性能的角度来看,这并不是一个很好的选择,但是可以解决两个问题:
mCallbacks
修改不会中断循环这种折衷对我来说是可以接受的,我不会通知新添加的订户,也不会跳过最近删除的订户的通知。
我无法接受的解决方法是:
StorageType
的模板参数将导致iterateSubscribers
函数的编译错误一种可能的解决方案是将这样的容器或其value_type
定义为指针,但是由于我的原因,这负担不起。
我还介绍了另一种解决方案-不要执行复制,用递归互斥锁替换常规互斥锁,并在所持有的锁下执行所有调用,但这有一个问题。一旦元素被添加或删除,循环将被破坏:
for( auto it = callbacks.begin(); it != callbacks.end(); ++it )
是否还有其他技术/想法来实现所需的行为?
编辑:
我走在重构的道路上。将旧版回调容器的value_type从回调副本更改为shared_ptr。这使我能够复制容器,而不必依赖于低估value_type的TriviallyCopyable状态。