简介本文介绍sigslot这个库 它是用C实现的具有类型安全线程安全的信号和槽机制的库。它完全使用C语言进行编写只有一个头文件因此避免了在使用的时候进行源码的预编译。sigslot的主页是http://sigslot.sourceforge.net。一个Signal/Slot的例子在传统的C代码中类之间的沟通是必须通过调用彼此的成员方法这也就使得每个类要在定义的时候就要指定调用的成员方法以及其说所属的类进行。例如class Switch { public: virtual void Clicked() 0; }; class Light { public: void ToggleState(); void TurnOn(); void TurnOff(); };在不改变Switch或Light类的情况下,让Switch类的Clicked方法控制Light实例的状态那么我们需要这样做class ToggleSwitch : public Switch { public: ToggleSwitch(Light lp) { m_lp lp; } virtual void Clicked() { m_lp.ToggleState(); } private: Light m_lp; }; Light lp1, lp2; ToggleSwitch tsw1(lp1), tsw2(lp2);虽然它也能够工作但是它并不完美最好的方法就是使用信号和槽 在Switch类中不需要考虑控制的是灯还是风扇在Light类中不需要考虑控制这个灯使用的什么样的开关。下边我们就使用库sigslot进行Switch和Light类的改造。class Switch { public: sigslot::signal0 Clicked; }; class Light : public sigslot::has_slots { public: void ToggleState(); void TurnOn(); void TurnOff(); }; Light lp1, lp2; Switch sw1, sw2;主要改动在Switch类中纯虚函数Clicked(), 被一个类型为sigslot::signal0的成员方法取代。在Lightl类中Light类继承于类sigslot::has_slots.修改之后如果希望让Switch控制Light的状态只需要使用下边的方法将Switch的实例和Light的实例进行连接。sw1.Clicked.connect(lp1, Light::ToggleState()); sw2.Clicked.connect(lp2, Light::ToggleState());如果你想再加两盏灯而且每个灯也都有自己的开完我们也再添加两个开关一个开关可以关闭所有的灯一个可以打开所有的灯我们也只需要进行简单的修改。Light lp3, lp4; Switch sw3, sw4, all_on, all_off; sw3.Clicked.connect(lp3, Light::ToggleState()); sw4.Clicked.connect(lp4, Light::ToggleState()); all_on.Clicked.connect(lp1, Light::TurnOn()); all_on.Clicked.connect(lp2, Light::TurnOn()); all_on.Clicked.connect(lp3, Light::TurnOn()); all_on.Clicked.connect(lp4, Light::TurnOn()); all_off.Clicked.connect(lp1, Light::TurnOff()); all_off.Clicked.connect(lp2, Light::TurnOff()); all_off.Clicked.connect(lp3, Light::TurnOff()); all_off.Clicked.connect(lp4, Light::TurnOff());参数类型Signal和Slots也是可以带一个或多个指定类型的参数。库sigslot是建立在C的templated机制上的所以在signal和solts的声明和调用的时候都会进行完全的类型检查。signal类型的命名规则是signalntype1, type2 n是参数的个数。在下边的这个例子中windows在状态发生变化的时候就会发出一系列的信号在MyControl的实例对象中就可以对这些信号进行处理。class Window { public: enum WindowState { Minimised, Normal, Maximised }; signal1WindowState StateChanged; signal2int, int MovedTo; signal2int, int Resized; }; class MyControl : public Control, public has_slots { public: void OnStateChanged(WindowState ws); void OnMovedTo(int x, int y); void OnResize(int x, int y); }; Window w; MyControl c; w.StateChanged.connect(c, MyControl::OnStateChanged); w.MovedTo.connect(c, MyControl::OnMovedTo); w.Resized.connect(c, MyControl::OnResize);信号signal和槽slot的命名不是关键的主要是singal和slot的参数类型和个数要一致。库的使用发送信号例如信号signal2char *, int ReportError;我们使用如下两种方法来发送这个信号1. ReportError(Something went wrong, ERR_SOMETHING_WRONG); 2. ReportError.emit(Something went wrong, ERR_SOMETHING_WRONG);方法1是在类signal2 中对() 的重载实际调用的还是方法emit()连接信号和槽信号的连接是使用方法类signaln的成员方法connect().方法connect()需要指定两个参数1 指向目标实例的指针2 指向目标实例的成员方法的指针为了让方法connect()能够工作需要目标实例的类是继承于类has_slots.一个信号是可以连接到任意数量的槽上的当信号被发送的时候所以连接到这个信号上的槽都会被调用。槽的调用对信号是无感的所以槽方法的返回值都是void。在现在的实现中槽方法是使用std::list进行存储的这也就意味着当一个信号连接了多个槽的时候槽方法的调用顺序和其被连接的顺序是一致的。在未来的版本中可能会对这个行为进行修改因此我们不能确保槽方法的调用顺序一定会和其被连接的顺序一致。断开信号和槽的连接我们很少会用到断开信号的操作 因为当信号实体或者槽实体被析构的时候信号与槽的连接也就被断开了。但是在需要的时候我们还是可以调用方法signal的成员方法disconnec()来断开连接。例如w.Resized.disconnect(c);断开w的Resized信号和槽方法MyControl::OnResize的连接。槽方法的实现槽方法是具有如下属性的普通成员方法返回类型必须是void携带0到8个任意类型的参数必须继承于has_slots槽方法可以在信号槽机制中被调用也可以作为普通方法被调用。断开信号的所用连接signal的成员方法disconnec_all()可以用来断开与该信号连接的所有槽如:all_on.Clicked.disconnec_all(); all_off.Clicked.disconnec_all();此后开关all_on将不能打开所有灯。断开槽对象中所用连接has_slots的成员方法disconnec_all()可以用来断开与该目标对象连接的所有信号lp3.disconnec_all();此后灯lp3将不受开关控制发送一个槽连接的信号信号会将被忽略不会有错误生成这样可以简化程序的编写。如下所示一个可重用的字符串可视化编辑控件类class StringEdit : public has_slots { public: signal0 OnReturnPressed; signal0 OnTabPressed; signal1char OnKeyPressed; signal1char * OnTextChanged; void SetText(char* text); // Slot void ClearText(); // Slot ... };没有错误发生所以每个信号在发送的时候就不用进行烦人的is_connected检查。信号的线程安全signal提供了三种信号安全模式1 single_threaded单线程没有锁的保护2 multi_threaded_global所有信号共享同一个锁锁的创建消耗小但是锁的粒度大3 multi_threaded_local每个信号都有一个锁锁的创建开销大但是粒度小使用方法如下通过Template的最后一个参数指定。// Single-threaded signal1int, single_threaded Sig1; // Multithreaded Global signal1int, multi_threaded_global Sig2; // Multithreaded Local signal1int, multi_threaded_local Sig3槽的线程安全槽函数中的线程安全需要开发者自己来维护但是signal也提供了两个可用的锁1class MyMultithreadedClass : public has_slotsmulti_threaded_local { public: void Entry1() // Slot { lock(); ... unlock(); } void Entry2() // Slot { lock(); ... unlock(); } };2class MyMultithreadedClass : public has_slotsmulti_threaded_local { public: void Entry1() // Slot { lock_blockmulti_threaded_local lock(this); ... } void Entry2() { lock_blockmulti_threaded_local lock(this); ... } }