- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
最近我着手实现一个使用“观察者模式”的消息调度系统:这里没什么特别的。在我开发它时,我认为从“主题”发送“消息”对象会很好,这些对象可能彼此根本不同,并且可以从许多“观察者”那里读取。
这些不同的消息采用不同消息类别的形式(例如,考虑“用户注销消息”、“屏幕模式切换”和“音量级别已更改”,所有这些都需要不同的信息),很快我发现“观察者”不需要知道我想要创建的每条不同消息(至少可以说,那将是……不可持续的)。相反,我希望每个观察者都能够对特定类型的消息使用react。
因此,为了做出一些东西,我认为双重分派(dispatch)可能是我的选择。一点点之后我得到了这篇文章(c++11 只是因为 for 循环):
#include <iostream>
#include <vector>
#include <string>
/**
* A few forward declarations.
*/
class Message_base;
class Message_type_a;
class Message_type_b;
/**
* Base observer...
*/
class Observer_base
{
public:
/**
* All these implementations are empty so we don't have to specify them
* in every single derived class.
*/
virtual void get_message(const Message_base&) {}
virtual void get_message(const Message_type_a&) {}
virtual void get_message(const Message_type_b&) {}
};
/**
* Specification of all message types.
*/
class Message_base
{
public:
/**
* This is the method that will implement the double dispatching in all
* derived classes.
*/
virtual void be_recieved(Observer_base &r) const=0; //Now that's a nasty method name.
};
class Message_type_a:public Message_base
{
private:
int integer_value;
public:
Message_type_a(int v):integer_value(v) {}
int get_integer_value() const {return integer_value;}
void be_recieved(Observer_base &r) const {r.get_message(*this);}
};
class Message_type_b:public Message_base
{
private:
std::string string_value;
public:
Message_type_b(const std::string v):string_value(v) {}
std::string get_string_value() const {return string_value;}
void be_recieved(Observer_base &r) const {r.get_message(*this);}
};
/**
* This is the base clase for the Subject... Notice that there are no virtual
* methods so we could as well instantiate this class instead of derive it.
*/
class Subject_base
{
private:
std::vector<Observer_base *> observers;
public:
void emit_message(const Message_base& m) {for(auto o : observers) m.be_recieved(*o);} //Again, nasty to read since it's... backwards.
void register_observer(Observer_base * o) {observers.push_back(o);}
};
/**
* Now we will create a subject class for the sake of it. We could just call the
* public "emit_message" from main passing Message objects.
*/
class Subject_derived:public Subject_base
{
public:
void emit_message_a(int v) {emit_message(Message_type_a(v));}
void emit_message_b(const std::string v) {emit_message(Message_type_b(v));}
};
/**
* This gets fun... We make two observers. One will only get type_a messages
* and the other will get type_b.
*/
class Observer_type_a:public Observer_base
{
private:
int index; //We will use it to identify the observer.
public:
Observer_type_a(int i):index(i) {}
void get_message(const Message_type_a& m) {std::cout<<"Observer_type_a ["<<index<<"] : got type_a message : "<<m.get_integer_value()<<std::endl;}
};
class Observer_type_b:public Observer_base
{
private:
std::string name; //Merely to identify the observer.
public:
Observer_type_b(const std::string& n):name(n) {}
void get_message(const Message_type_b& m) {std::cout<<"Observer_type_b ["<<name<<"] : got type_b message : "<<m.get_string_value()<<std::endl;}
};
/**
* Stitch all pieces together.
*/
int main(int argc, char ** argv)
{
Observer_type_a o_a1(1);
Observer_type_a o_a2(2);
Observer_type_b o_b1("Sauron");
Observer_type_b o_b2("Roverandom");
Subject_derived s_a;
s_a.register_observer(&o_a1);
s_a.register_observer(&o_b1);
s_a.emit_message_a(23);
s_a.emit_message_b("this is my content");
s_a.register_observer(&o_a2);
s_a.register_observer(&o_b2);
s_a.emit_message_a(99);
s_a.emit_message_b("this is my second content");
//gloriously exit.
return 0;
}
为了清楚起见,我将在这里说出我的目标:
我的问题来了:我是否错过了一个更简单的实现来实现我的目标?
值得一提的是,将使用此系统的系统不会有那么多观察者,同时出现的对象可能少于十个。
最佳答案
一些元编程样板:
// a bundle of types:
template<class...>struct types{using type=types;};
// a type that does nothing but carry a type around
// without being that type:
template<class T>struct tag{using type=T;};
// a template that undoes the `tag` operation above:
template<class Tag>using type_t=typename Tag::type;
// a shorter way to say `std::integral_constant<size_t, x>`:
template<std::size_t i>struct index:std::integral_constant<std::size_t, i>{};
获取类型在 types<...>
中的索引:
// this code takes a type T, and a types<...> and returns
// the index of the type in there.
// index_of
namespace details {
template<class T, class Types>
struct index_of{};
}
template<class T, class Types>
using index_of_t=type_t<details::index_of<T,Types>>;
namespace details {
// if the first entry in the list of types is T,
// our value is 0
template<class T, class...Ts>struct index_of<T, types<T,Ts...>>:
tag< index<0> >
{};
// otherwise, it is 1 plus our value on the tail of the list:
template<class T, class T0, class...Ts>
struct index_of<T, types<T0, Ts...>>:
tag< index< index_of_t<T,types<Ts...>{}+1 > >
{};
}
这是一个单一的“ channel ”广播者(它发送一种消息):
// a token is a shared pointer to anything
// below, it tends to be a shared pointer to a std::function
// all we care about is the lifetime, however:
using token = std::shared_ptr<void>;
template<class M>
struct broadcaster {
// f is the type of something that can eat our message:
using f = std::function< void(M) >;
// we keep a vector of weak pointers to people who can eat
// our message. This lets them manage lifetime independently:
std::vector<std::weak_ptr<f>> listeners;
// reg is register. You pass in a function to eat the message
// it returns a token. So long as the token, or a copy of it,
// survives, broadcaster will continue to send stuff at the
// function you pass in:
token reg( f target ) {
// if thread safe, (write)lock here
auto sp = std::make_shared<f>(std::move(target));
listeners.push_back( sp );
return sp;
// unlock here
}
// removes dead listeners:
void trim() {
// if thread safe, (try write)lock here
// and/or have trim take a lock as an argument
listeners.erase(
std::remove_if( begin(listeners), end(listeners), [](auto&& p){
return p.expired();
} ),
listeners.end()
);
// unlock here
}
// Sends a message M m to every listener who is not dead:
void send( M m ) {
trim(); // remove dead listeners
// (read) lock here
auto tmp_copy = listeners; // copy the listeners, just in case
// unlock here
for (auto w:tmp_copy) {
auto p = w.lock();
if (p) (*p)(m);
}
}
};
这里是多 channel subject
可以支持任意数量的不同消息类型(在编译时确定)。如果您无法匹配消息类型,send
和/或 reg
将无法编译。您有责任决定消息是否为 const&
或值(value)或其他。试图 reg
右值消息将不起作用。目的是M
传递给 reg
和 send
明确地。
// fancy wrapper around a tuple of broadcasters:
template<class...Ts>
struct subject {
std::tuple<broadcaster<Ts>...> stations;
// helper function that gets a broadcaster compatible
// with a message type M:
template<class M>
broadcaster<M>& station() {
return std::get< index_of_t<M, types<Ts...>>{} >( stations );
}
// register a message of type M. You should call with M explicit usually:
template<class M>
token reg( std::function<void(M)> listener ) {
return station<M>().reg(std::move(listener));
}
// send a message of type M. You should explicitly pass M usually:
template<class M>
void send( M m ) {
station<M>().send(std::forward<M>(m));
}
};
当你reg
, 它返回一个 token
, 又名 std::shared_ptr<void>
.只要此 token (或拷贝)存在,消息就会流动。如果它消失,发送到 reged 回调的消息将结束。通常这意味着听众应该维护一个 std::vector<token>
, 以及使用 this
的 reg lambdas愿不愿意。
在 C++14/1z 中,上面的代码变得更好一些(我们可以去掉 types<...>
和 index_of
一个)。
如果你在一个广播周期内添加一个监听器,它不会被发送到。如果您在广播周期中删除了一个监听器,它将不会在您删除它之后被发送到。
线程安全注释是为广播者的读写锁设置的。
当 trim
时,为给定广播者的死听众分配的内存被回收或 send
叫做。然而,std::function
很久以前就会被销毁,所以只有有限数量的内存被浪费直到下一个send
.然后我就这样做了,因为无论如何我们都要遍历消息列表,不妨先清理一下乱七八糟的东西。
此解决方案没有 RTTI 或动态转换,消息仅发送给理解它们的听众。
在c++17事情变得简单了。删除所有元编程样板,删除 subject
(保留 broadcaster
)并执行此操作以处理多个 channel :
template<class...Ms>
struct broadcasters : broadcaster<Ms>... {
using broadcaster<Ms>::reg...;
using broadcaster<Ms>::send...;
template<class M>
broadcaster<M>& station() { return *this; }
};
这个broadcasters
现在几乎是对 subject
的改进以上。
由于 std::function
的改进自 c++11 , reg
函数通常做正确的事情,除非信号选项过于相似。如果您确实遇到了 reg
的问题或 send
,你被迫调用.station<type>().reg(blah)
.
但是 99/100 次你可以只做 .reg( lambda )
和 .send( msg )
重载决议做正确的事。
这里是整个系统增加了一个模块化的嵌入式线程安全系统:
struct not_thread_safe {
struct not_lock {~not_lock(){}};
auto lock() const { return not_lock{}; }
};
struct mutex_thread_safe {
auto lock() const { return std::unique_lock<std::mutex>(m); }
private:
mutable std::mutex m;
};
struct rw_thread_safe {
auto lock() { return std::unique_lock<std::shared_timed_mutex>(m); }
auto lock() const { return std::shared_lock<std::shared_timed_mutex>(m); }
private:
mutable std::shared_timed_mutex m;
};
template<class D, class>
struct derived_ts {
auto lock() { return static_cast<D*>(this)->lock(); }
auto lock() const { return static_cast<D const*>(this)->lock(); }
};
using token = std::shared_ptr<void>;
template<class M, class TS=not_thread_safe>
struct broadcaster:
TS
{
using f = std::function< void(M) >;
mutable std::vector<std::weak_ptr<f>> listeners;
token reg( f target )
{
auto l = this->lock();
auto sp = std::make_shared<f>(std::move(target));
listeners.push_back( sp );
return sp;
}
// logically const, but not really:
void trim() const {
auto l = const_cast<broadcaster&>(*this).lock();
auto it = std::remove_if( listeners.begin(), listeners.end(), [](auto&& p){
return p.expired();
} );
listeners.erase( it, listeners.end() );
}
// logically const, but not really:
void send( M m ) const
{
trim(); // remove dead listeners
auto tmp_copy = [this]{
auto l = this->lock();
return listeners; // copy the listeners, just in case
}();
for (auto w:tmp_copy) {
auto p = w.lock();
if (p) (*p)(m);
}
}
};
template<class TS, class...Ms>
struct basic_broadcasters :
TS,
broadcaster<Ms, derived_ts<basic_broadcasters<TS, Ms...>, Ms> >...
{
using TS::lock;
using broadcaster<Ms, derived_ts<basic_broadcasters<TS, Ms...>, Ms> >::reg...;
using broadcaster<Ms, derived_ts<basic_broadcasters<TS, Ms...>, Ms> >::send...;
template<class M>
broadcaster<M, derived_ts<basic_broadcasters<TS, Ms...>, M>>& station() { return *this; }
template<class M>
broadcaster<M, derived_ts<basic_broadcasters<TS, Ms...>, M>> const& station() const { return *this; }
};
template<class...Ms>
using broadcasters = basic_broadcasters<rw_thread_safe, Ms...>;
broadcasters<Messages...>
现在是一个读写锁定的广播类,它使用 1 个公共(public)共享锁来同步每个广播队列。
basic_broadcasters<not_thread_safe, Messages...>
而是创建一个没有锁定的(即,不是线程安全的)。
关于c++ - 消息系统的观察者模式+访客模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32079697/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!