通过在C ++ 11中进行的更改(例如包含std::bind
),是否有推荐的方法来实现简单的单线程观察者模式,而不依赖于核心语言或标准之外的任何内容库(如boost::signal
)?
修改
如果有人可以发布一些代码,显示如何使用新语言功能减少对boost::signal
的依赖,那么这仍然非常有用。
答案 0 :(得分:28)
我认为bind
让更容易创建广告位(cfr。'preferred' syntax vs. the 'portable' syntax - 这一切都将消失)。然而,观察员管理层并没有变得那么复杂。
但是作为@R。 Martinho Fernandes提到:现在可以轻松创建std::vector<std::function< r(a1) > >
而无需(人工)“纯虚拟”接口类的麻烦。
根据要求:关于连接管理的想法 - 可能充满了错误,但你会明白:
// note that the Func parameter is something
// like std::function< void(int,int) > or whatever, greatly simplified
// by the C++11 standard
template<typename Func>
struct signal {
typedef int Key; //
Key nextKey;
std::map<Key,Func> connections;
// note that connection management is the same in C++03 or C++11
// (until a better idea arises)
template<typename FuncLike>
Key connect( FuncLike f ) {
Key k=nextKey++;
connections[k]=f;
return k;
}
void disconnect(Key k){
connections.erase(k);
}
// note: variadic template syntax to be reviewed
// (not the main focus of this post)
template<typename Args...>
typename Func::return_value call(Args... args){
// supposing no subcription changes within call:
for(auto &connection: connections){
(*connection.second)(std::forward(...args));
}
}
};
用法:
signal<function<void(int,int)>> xychanged;
void dump(int x, int y) { cout << x << ", " << y << endl; }
struct XY { int x, y; } xy;
auto dumpkey=xychanged.connect(dump);
auto lambdakey=xychanged.connect([&xy](int x, int y){ xy.x=x; xy.y=y; });
xychanged.call(1,2);
答案 1 :(得分:3)
由于您要求代码,我的博客条目Performance of a C++11 Signal System包含基于C ++ 11功能的完整功能信号系统的单文件实现,没有进一步的依赖性(尽管是单线程的,这是一个表现要求)。
以下是一个简短的用法示例:
Signal<void (std::string, int)> sig2;
sig2() += [] (std::string msg, int d) { /* handler logic */ };
sig2.emit ("string arg", 17);
可在此unit test中找到更多示例。
答案 2 :(得分:2)
我写了自己的轻量级Signal / Slot类,返回连接句柄。面对异常,现有答案的关键系统非常脆弱。在显式调用中删除内容时必须格外小心。我更喜欢使用RAII来打开/关闭对。
我的图书馆中一个值得注意的缺乏支持是能够从您的电话中获取返回值。我相信boost :: signal有计算总回报值的方法。在实践中,通常你不需要这个,我只是觉得它很混乱,但是我可能会想出一种有趣的回归方法作为未来的练习。
关于我的类的一个很酷的事情是Slot和SlotRegister类。 SlotRegister提供了一个公共接口,您可以安全地链接到私有插槽。这可以防止外部对象调用您的观察者方法。这很简单,但封装很好。
但我不相信我的代码是线程安全的。
//"MIT License + do not delete this comment" - M2tM : http://michaelhamilton.com
#ifndef __MV_SIGNAL_H__
#define __MV_SIGNAL_H__
#include <memory>
#include <utility>
#include <functional>
#include <vector>
#include <set>
#include "Utility/scopeGuard.hpp"
namespace MV {
template <typename T>
class Signal {
public:
typedef std::function<T> FunctionType;
typedef std::shared_ptr<Signal<T>> SharedType;
static std::shared_ptr< Signal<T> > make(std::function<T> a_callback){
return std::shared_ptr< Signal<T> >(new Signal<T>(a_callback, ++uniqueId));
}
template <class ...Arg>
void notify(Arg... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}
template <class ...Arg>
void operator()(Arg... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}
void block(){
isBlocked = true;
}
void unblock(){
isBlocked = false;
}
bool blocked() const{
return isBlocked;
}
//For sorting and comparison (removal/avoiding duplicates)
bool operator<(const Signal<T>& a_rhs){
return id < a_rhs.id;
}
bool operator>(const Signal<T>& a_rhs){
return id > a_rhs.id;
}
bool operator==(const Signal<T>& a_rhs){
return id == a_rhs.id;
}
bool operator!=(const Signal<T>& a_rhs){
return id != a_rhs.id;
}
private:
Signal(std::function<T> a_callback, long long a_id):
id(a_id),
callback(a_callback),
isBlocked(false){
}
bool isBlocked;
std::function< T > callback;
long long id;
static long long uniqueId;
};
template <typename T>
long long Signal<T>::uniqueId = 0;
template <typename T>
class Slot {
public:
typedef std::function<T> FunctionType;
typedef Signal<T> SignalType;
typedef std::shared_ptr<Signal<T>> SharedSignalType;
//No protection against duplicates.
std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
auto signal = Signal<T>::make(a_callback);
observers.insert(signal);
return signal;
} else{
return nullptr;
}
}
//Duplicate Signals will not be added. If std::function ever becomes comparable this can all be much safer.
bool connect(std::shared_ptr<Signal<T>> a_value){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
observers.insert(a_value);
return true;
}else{
return false;
}
}
void disconnect(std::shared_ptr<Signal<T>> a_value){
if(!inCall){
observers.erase(a_value);
} else{
disconnectQueue.push_back(a_value);
}
}
template <typename ...Arg>
void operator()(Arg... a_parameters){
inCall = true;
SCOPE_EXIT{
inCall = false;
for(auto& i : disconnectQueue){
observers.erase(i);
}
disconnectQueue.clear();
};
for (auto i = observers.begin(); i != observers.end();) {
if (i->expired()) {
observers.erase(i++);
} else {
auto next = i;
++next;
i->lock()->notify(std::forward<Arg>(a_parameters)...);
i = next;
}
}
}
void setObserverLimit(size_t a_newLimit){
observerLimit = a_newLimit;
}
void clearObserverLimit(){
observerLimit = std::numeric_limits<size_t>::max();
}
int getObserverLimit(){
return observerLimit;
}
size_t cullDeadObservers(){
for(auto i = observers.begin(); i != observers.end();) {
if(i->expired()) {
observers.erase(i++);
}
}
return observers.size();
}
private:
std::set< std::weak_ptr< Signal<T> >, std::owner_less<std::weak_ptr<Signal<T>>> > observers;
size_t observerLimit = std::numeric_limits<size_t>::max();
bool inCall = false;
std::vector< std::shared_ptr<Signal<T>> > disconnectQueue;
};
//Can be used as a public SlotRegister member for connecting slots to a private Slot member.
//In this way you won't have to write forwarding connect/disconnect boilerplate for your classes.
template <typename T>
class SlotRegister {
public:
typedef std::function<T> FunctionType;
typedef Signal<T> SignalType;
typedef std::shared_ptr<Signal<T>> SharedSignalType;
SlotRegister(Slot<T> &a_slot) :
slot(a_slot){
}
//no protection against duplicates
std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){
return slot.connect(a_callback);
}
//duplicate shared_ptr's will not be added
bool connect(std::shared_ptr<Signal<T>> a_value){
return slot.connect(a_value);
}
void disconnect(std::shared_ptr<Signal<T>> a_value){
slot.disconnect(a_value);
}
private:
Slot<T> &slot;
};
}
#endif
Supplimental scopeGuard.hpp:
#ifndef _MV_SCOPEGUARD_H_
#define _MV_SCOPEGUARD_H_
//Lifted from Alexandrescu's ScopeGuard11 talk.
namespace MV {
template <typename Fun>
class ScopeGuard {
Fun f_;
bool active_;
public:
ScopeGuard(Fun f)
: f_(std::move(f))
, active_(true) {
}
~ScopeGuard() { if(active_) f_(); }
void dismiss() { active_ = false; }
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: f_(std::move(rhs.f_))
, active_(rhs.active_) {
rhs.dismiss();
}
};
template<typename Fun>
ScopeGuard<Fun> scopeGuard(Fun f){
return ScopeGuard<Fun>(std::move(f));
}
namespace ScopeMacroSupport {
enum class ScopeGuardOnExit {};
template <typename Fun>
MV::ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) {
return MV::ScopeGuard<Fun>(std::forward<Fun>(fn));
}
}
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= MV::ScopeMacroSupport::ScopeGuardOnExit() + [&]()
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__)
#endif
}
#endif
使用我的库的示例应用程序:
#include <iostream>
#include <string>
#include "signal.hpp"
class Observed {
private:
//Note: This is private to ensure not just anyone can spawn a signal
MV::Slot<void (int)> onChangeSlot;
public:
typedef MV::Slot<void (int)>::SharedSignalType ChangeEventSignal;
//SlotRegister is public, users can hook up signals to onChange with this value.
MV::SlotRegister<void (int)> onChange;
Observed():
onChange(onChangeSlot){ //Here is where the binding occurs
}
void change(int newValue){
onChangeSlot(newValue);
}
};
class Observer{
public:
Observer(std::string a_name, Observed &a_observed){
connection = a_observed.onChange.connect([=](int value){
std::cout << a_name << " caught changed value: " << value << std::endl;
});
}
private:
Observed::ChangeEventSignal connection;
};
int main(){
Observed observed;
Observer observer1("o[1]", observed);
{
Observer observer2("o[2]", observed);
observed.change(1);
}
observed.change(2);
}
上述输出将是:
o[1] caught changed value: 1
o[2] caught changed value: 1
o[1] caught changed value: 2
如您所见,插槽会自动断开死信号。
答案 3 :(得分:0)
我自己也去过这个。我的努力可以在这个要点中找到,它将继续发展。 。 。
https://gist.github.com/4172757
我使用不同的样式,更类似于JUCE中的更改通知而不是BOOST信号。使用一些lambda语法完成连接管理,该语法通过复制进行捕获。到目前为止它运作良好。
答案 4 :(得分:0)
这就是我的想法。
这假定不需要汇总广播信号的侦听器的结果。 同样,“ slot”或Signal :: Listener是回调的所有者。 这应该与lambda可能捕获的对象一起使用,以便当该对象超出范围时,回调也将超出范围,从而防止再次调用该对象。
您还可以使用其他答案中描述的方法,以一种可以查找的方式存储Listener所有者对象。
template <typename... FuncArgs>
class Signal
{
using fp = std::function<void(FuncArgs...)>;
std::forward_list<std::weak_ptr<fp> > registeredListeners;
public:
using Listener = std::shared_ptr<fp>;
Listener add(const std::function<void(FuncArgs...)> &cb) {
// passing by address, until copy is made in the Listener as owner.
Listener result(std::make_shared<fp>(cb));
registeredListeners.push_front(result);
return result;
}
void raise(FuncArgs... args) {
registeredListeners.remove_if([&args...](std::weak_ptr<fp> e) -> bool {
if (auto f = e.lock()) {
(*f)(args...);
return false;
}
return true;
});
}
};
用法
Signal<int> bloopChanged;
// ...
Signal<int>::Listener bloopResponse = bloopChanged.add([](int i) { ... });
// or
decltype(bloopChanged)::Listener bloopResponse = ...
// let bloopResponse go out of scope.
// or re-assign it
// or reset the shared_ptr to disconnect it
bloopResponse.reset();
我也为此做了一个要点,并给出了更深入的示例: https://gist.github.com/johnb003/dbc4a69af8ea8f4771666ce2e383047d