Skip to content
Freeman Zhang edited this page Dec 20, 2016 · 20 revisions

Quick Guide

Build and Installation

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 is OFF.
  • 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.

Declare Signal

Use the template class sigcxx::Signal<> to declare a signal. 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 signal to public is return a sigcxx::SignalRef<> in a public member function, which give a reference to signal object.

For example, suppose you are developing a GUI project, you may need a clicked signal in a button class:

Example 1. Button class with a 'clicked' signal

class Button: public Widget
{
public:

    // ...

    // Signal connection interface
    sigcxx::SignalRef<> clicked () { return clicked_; }

private:

    // Signal implementation
    sigcxx::Signal<> clicked_;
};

Note: a signal object is not copyable. e.g.

sigcxx::Signal<> event1;
sigcxx::Signal<> event2(event1);	// Error
sigcxx::Signal<> event3;
event3 = event1;	// Error

Arguments

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;

Trackable object

A signal 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 (int arg1, int arg2, const sigcxx::Slot* slot = nullptr);
  
  // A regular method
  void onInvalidUpdate (int arg1, int arg2);
};

Note: In sigcxx, signals can only be connected to special methods whose last argument MUST be const sigcxx::Slot*. It works like a signature to mark this kind of method is a slot, and it can provide important information in runtime for fast process. So in the example above, only the onValidUpdate can be connected.

There's a typedef of sigcxx::SLOT in sigcxx you can use:

namespace sigcxx {
  typedef Slot *SLOT;
}

Or you may use a macro to make the slot method cleaner:

#define __SLOT__ sigcxx::Slot* slot = nullptr

class Widget: public sigcxx::Trackable
{
  // ...
  
  void onValidUpdate (int arg1, int arg2, __SLOT__);
};

(If you do want to call any method, consider using sigcxx::Delegate.)

Connect Signal

Signals can be connected to signal handlers. In sigcxx, signal 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: Again, you cannot connect signal 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 signal connections will be removed safely.

Example

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 (__SLOT__);
};

Button* button = new Button;
Label* label = new Label;

button->clicked().Connect(label, &Label::OnUpdate);

Multiple Connections

Note: If you call the Connect() multiple times, it will create the same number of connections.

button->clicked().Connect(label, &Label::OnUpdate);  // 1
button->clicked().Connect(label, &Label::OnUpdate);  // 2
button->clicked().Connect(label, &Label::OnUpdate);  // 3
// now the button's 'clicked' signal has 3 connections

This is because the Connect() does not check if the delegate already exists, this is used to provide the multicast.

Insert Position

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.

Emit Signal

After a signal is connected to a member function, you can emit the signal at the appropriate time in your application, with the same variadic arguments you decalre the signal.

Example 3. Fire the button's clicked signal.

class Button: public Widget
{
public:

    // ...

    // Signal connection interface
    sigcxx::SignalRef<> clicked () { return clicked_; }

protected:

  virtual void mouseDown (Input* input) override
  {
    // do sth...
    clicked_.Emit();  // now emit the signal
  }

private:

    // Signal implementation
    sigcxx::Signal<> clicked_;
};

Disconnect Signal

To disconnect the signals, use:

  • DisconnectAll (T* obj, void (T::*method) (ParamTypes..., SLOT))
  • Disconnect (T* obj, void (T::*method) (ParamTypes..., SLOT), int start_pos, int counts)

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 the maximal counts number of connections.

For example:

button->clicked().Connect(lbl, &Label::OnUpdate);  // #1
button->clicked().Connect(lbl, &Label::OnUpdate);  // #2
button->clicked().Connect(lbl, &Label::OnUpdate);  // #3

button->clicked().Disconnect(label, &Label::OnUpdate); // This will remove the #3 connection.
button->clicked().DisconnectAll(lbl, &Label::OnUpdate);  // This will remove the remaining #1,#2 connections.

Connection control in Trackable

You can delete the current trackable object in the slot method.

void Label::OnUpdate (sigcxx::SLOT /* slot */)
{
  delete this;
}

And in the slot method, you can still use Disconnect(), DisconnectAll(), or UnbindAll(), Unbind() provided by sigcxx::Trackable.

void Label::OnUpdate (sigcxx::SLOT /* slot */)
{
  if (condition) {
     UnbindAll(&Label::OnUpdate);
  }
}

Multicast

Note: sigcxx::Signal<> supports multicast, when you emit a signal, it will invoke all connected methods.

Example
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 emit the clicked signal, all three OnUpdate() member functions in different objects will be invoked in order.

Signal Chaining

A sigcxx::Signal<> object can be used as an signal handler too, as the sigcxx::Signal<> is also a trackable object.

Example

Example 4. Signal chaining

Button* btn1 = new Button;
Button* btn2 = new Button;

btn1->clicked().Connect(btn2->clicked_);

Note:, here I suppose the clicked_ signal in btn2 is accessable.

Click btn1 will emit clicked_ signal in btn2.

You can disconnect the connection to another signal by:

  • DisconnectAll()
  • Disconnect()

Same as the member function situation above.

For example:

btn1->clicked().DisconnectOnce(btn2->clicked_);
btn1->clicked().DisconnectAll(btn2->clicked_);

Note: You can remove all connections to member functions and signals with DisconnectAll().

Virtual Member Function

Member function which is connected to a signal can be virtual and abstract (pure virtual).

Example 5. Connect signal to a virtual method

class Widget: public sigcxx::Observer;

class AbstractDialog: public Widget
{
public:
  AbstractDialog (Widget* parent);

  virtual void OnUpdate (__SLOT__) = 0; // pure virtual

};

class Dialog: public AbstractDialog
{
public:
    Dialog (Widget* parent);
    virtual void OnUpdate (__SLOT__) = override;
};

AbstractDialog *dlg = new Dialog();
btn1->clicked().Connect(dlg, &AbstractDialog::OnUpdate);

Complex Example

Example

Performance

Faster than boost::signal2 and Qt signal-slot.

Advice On Multi-thread Programming

Warning: sigcxx does not provide any protection in multi-thread environment. You should take care of the racecondition in your code.

TBD

Use Delegate Only

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)>::FromMethod(&obj, &Consumer::foo);

You can invoke (or call) the method through the delegate instance:

float ret = delegate(1, 2);

Construct

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

Invoke the delegate with arguments:

float ret = d1(1, 2);

Boolean

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