-
Notifications
You must be signed in to change notification settings - Fork 11
quick_guide
You can just drag the source code into your C++ project.
Or use cmake to build a dynamic or static library, just
$ cd <folder where you checkout the source>
$ mkdir build
$ cd build
$ cmake ../
$ make && sudo make install
By default, it will install header files to /usr/local/include/sigcxx
, and
library binary to /usr/local/lib
.
Available cmake options:
-
CMAKE_INSTALL_PREFIX
: this is a built-in cmake option, which you can use to specify where to install the headers and library. Default is/usr/local
. -
BUILD_UNIT_TEST
: A boolean option to compile the unit test code. Default isOFF
. -
WITH_QT5
: A boolean option to compile test code to compare this project with Qt5 signal-slot. Default:OFF
.
An example to use all these options:
$ cmake ../ -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_UNIT_TEST=ON -DWITH_QT5=ON
Note: To compile test code to compare with boost::signals, you need to install boost signals dev package. To compile test code to compare with Qt signal slot, you need to install Qt5 package.
Use the template class sigcxx::Signal<>
to declare an event. In most cases, you need to use it as a member variable in your class, you can put it in any area of public
, protected
, private
as you want. But the recommended way to expose the event to public is return a cpsigcxxSignalRef<>
in a public member function, which give a reference to event object.
For example, suppose you are developing a GUI project, you may need a
clicked
event in a button class:
Example 1. Button class with 'clicked' event
class Button: public Widget
{
public:
// ...
// Signal connection interface
inline sigcxx::SignalRef<> clicked ()
{return clicked_;}
private:
// Signal implementation
sigcxx::Signal<> clicked_;
};
Note: an event object is not copyable. e.g.
sigcxx::Signal<> event1;
sigcxx::Signal<> event2(event1); // Error
sigcxx::Signal<> event3;
event3 = event1; // Error
Signals can have arbitrary number of arguments (based on variadic templates support in C++11). Types of arguments are specified as arguments for template class sigcxx::Signal<>
.
For example:
using namespace sigcxx;
Signal<bool> event1;
Signal<const String&, size_t> event2;
An event can only be connected to an object (event handler) which inherits from sigcxx::Trackable
or its subclass.
Here's a simple example to define a Widget
class:
class Widget: public sigcxx::Trackable
{
// ...
// A public method to receive event from sigcxx::Signal<int, int>
void onValidUpdate (const sigcxx::Sender* sender, int arg1, int arg2);
// A regular method
void onInvalidUpdate (int arg1, int arg2);
};
Note: In sigcxx
, events can only be connected to special methods whose first argument MUST be const sigcxx::Sender*
. The first argument works like a signature to mark this kind of method is an event handler, and it can provide important information in runtime for fast process. So in the example above, only the onValidUpdate
can be connected.
(If you do want to call any method, consider using sigcxx::Delegate
.)
Signals can be connected to event handlers. In sigcxx
, event
handlers are member functions bound to the specific object. Internally sigcxx implement delegate in a way inspired by
Fast C++ Delegate: Boost.Function 'drop-in' replacement and multicast by JaeWook Choi.
Note: You cannot connect event to any member function. You
can only connect an event to member functions of a sigcxx::Trackable
or subclass. This design makes sure when the sigcxx::Trackable
object is
deleted, all event connection will be removed safely.
Connection is established by the connect()
method of the
sigcxx::SignalRef<>
. The method requires 3 arguments.
- A pointer to a
sigcxx::Trackable
object. - A pointer to the member function of the receiver object.
- The position where to insert the connection. Default is
-1
, which means append this new connection.
Example 2. Connect a button to a label widget.
class Widget: public sigcxx::Trackable;
class Label: public Widget
{
public:
Dialog (Widget* parent);
void OnUpdate (const sigcxx::Sender* sender);
};
Button* btn = new Button;
Label* lbl = new Label;
btn->clicked().connect(lbl, &Label::OnUpdate);
Note: If you call the connect()
multiple times, it will create the same number of connections.
btn->clicked().connect(lbl, &Label::OnUpdate); // 1
btn->clicked().connect(lbl, &Label::OnUpdate); // 2
btn->clicked().connect(lbl, &Label::OnUpdate); // 3
// now the btn's clicked event has 3 connections
This is because the connect()
does not check if the delegate already exists, to
provide the multicast.
sigcxx::Signal<>
holds a list to store connections, the third argument of connect()
assigns where to insert the new connection:
- if >= 0, insert in order, so
0
will always push front the new connection. - if < 0, insert in reverse order, so
-1
(the default) will always push back the new connection. - You can assign any number (in
int
) of the position, so a large positive number is the same as push back, a very negative is the same as push front.
After an event is connected to a member function, you can fire the event at the appropriate time in your application, with the same variadic arguments you decalre the event.
Example 3. Fire the button's clicked
event.
class Button: public Widget
{
public:
// ...
// Signal connection interface
inline sigcxx::SignalRef<> clicked ()
{return clicked_;}
protected:
virtual void mouseDown (Input* input) override
{
// do sth...
clicked_.Fire(); // now fire the event
}
private:
// Signal implementation
sigcxx::Signal<> clicked_;
};
There're 2 methods for disconnecting from member function:
disconnect_all (T* obj, void (T::*method) (const sigcxx::Sender*, ParamTypes...))
disconnect_once (T* obj, void (T::*method) (const sigcxx::Sender*, ParamTypes...), int start_pos = -1)
The first one will remove all connections match the delegate to the member function.
The second one will start to search the connection list forward or backward from the start_pos
, and remove only the first one found.
For example:
btn->clicked().connect(lbl, &Label::OnUpdate); // 1
btn->clicked().connect(lbl, &Label::OnUpdate); // 2
btn->clicked().connect(lbl, &Label::OnUpdate); // 3
btn->clicked().disconnect_once(lbl, &Label::OnUpdate); // This will remove the #3 connection.
btn->clicked().disconnect_all(lbl, &Label::OnUpdate); // This will remove the remaining #1,#2 connections.
You can disconnect the event when being invoked in an trackable object, but use Unbind...(const Sender* sender, ...)
.
For example, you may want to delete self when received an event:
void Label::OnUpdate (const sigcxx::Sender* sender)
{
UnbindOnce(sender);
delete this;
}
Note: sigcxx::Signal<>
supports multicast, when you fire an event, it will invoke all connected methods.
Button* btn = new Button;
Label* lbl1 = new Label;
Label* lbl2 = new Label;
Label* lbl3 = new Label;
btn->clicked().connect(lbl1, &Label::OnUpdate);
btn->clicked().connect(lbl2, &Label::OnUpdate);
btn->clicked().connect(lbl3, &Label::OnUpdate);
Now when fire the clicked
event, all three OnUpdate()
member functions in different objects will be invoked in order.
A sigcxx::SignalRef<>
object can be used as an event handler too, as the sigcxx::Signal<>
is also a trackable object.
Example 4. Signal chaining
Button* btn1 = new Button;
Button* btn2 = new Button;
btn1->clicked().connect(btn2->clicked());
Click btn1 will fire clicked()
event in btn2.
You can disconnect the connection to another event by:
disconnect_all()
disconnect_once()
Same as the member function situation above.
For example:
btn1->clicked().disconnect_once(btn2->clicked());
btn1->clicked().disconnect_all(btn2->clicked());
Note: You can remove all connections to member functions and events with disconnect_all()
.
Member function which is connected to an event can be virtual and abstract (pure virtual).
Example 5. Connect event to a virtual method
class Widget: public sigcxx::Observer;
class AbstractDialog: public Widget
{
public:
AbstractDialog (Widget* parent);
virtual void OnUpdate (const sigcxx::Sender* sender) = 0; // pure virtual
};
class Dialog: public AbstractDialog
{
public:
Dialog (Widget* parent);
virtual void OnUpdate (const sigcxx::Sender* sender) = override;
};
AbstractDialog *dlg = new Dialog();
btn1->clicked().connect(dlg, &AbstractDialog::OnUpdate);
Faster than boost::signal2 and Qt signal-slot.
Warning: sigcxx does not provide any protection in multi-thread environment. You should take care of the racecondition in your code.
TBD
A delegate is an object that represents references to methods with a particular parameter list and return type. You can instantiate a delegate with variadic template:
sigcxx::Delegate<typename ReturnType, typename ... ParamTypes>
For example:
class Consumer
{
public:
// ...
float foo (int arg1, int arg2);
float constfoo (int arg1, int arg2) const;
};
Consumer obj;
sigcxx::Delegate<float, int, int> delegate = sigcxx::Delegate<float, int, int>::from_method(&obj, &Consumer::foo);
You can invoke (or call) the method through the delegate instance:
float ret = delegate(1, 2);
A delegate object can be constructed in different ways:
using namespace sigcxx;
Delegate<float, int, int> d0; // Empty delegate
Delegate<float, int, int> d1(&obj, &Consumer::foo); // Construct a delegate points to a method
Delegate<float, int, int> d2(&obj, &Consumer::constfoot); // Construct a delegate points to a const method
Delegate<float, int, int> d3 = Delegate<float, int, int>::from_method(&obj, &Consumer::foo); // Construct with static method
Delegate<float, int, int> d4 = Delegate<float, int, int>::from_method(&obj, &Consumer::constfoo); // Construct with static method
Delegate<float, int, int> d5 = d4;
Delegate<float, int, int> d6(d5);
Invoke the delegate with arguments:
float ret = d1(1, 2);
The Delegate class override the bool
operator, returns true if it's assigned a method.
if(d0) {} // false
if(d1) {} // true
You can compare 2 delegates:
if(d0 == d1) {} // false
if(d4 == d5) {} // true
if(d3 != d4) {} // true
if(d3 < d4) {} // no meaning, but necessary if you store a delegate in a unordered containers, e.g. std::set
if(d3 > d4) {} // ditto
Or check if it points to the method:
if(d1.equal(&obj, &Consumer::foo)) {} // true
Table of contents
- Quick Guide
- Delegate Implementation