2.6.0
Freundlich's C++ toolkit
Classes | Typedefs
fcppt.signal

Description

An implementation of a managed signals and slots system (replacement for boost's signals).

Introduction

From the introduction of boost.signals:

The Boost.Signals library is an implementation of a managed signals and slots system. Signals represent callbacks with multiple targets, and are also called publishers or events in similar systems. Signals are connected to some set of slots, which are callback receivers (also called event targets or subscribers), which are called when the signal is "emitted."

Signals and slots are managed, in that signals and slots (or, more properly, objects that occur as part of the slots) track all connections and are capable of automatically disconnecting signal/slot connections when either is destroyed. This enables the user to make signal/slot connections without expending a great effort to manage the lifetimes of those connections with regard to the lifetimes of all objects involved.

When signals are connected to multiple slots, there is a question regarding the relationship between the return values of the slots and the return value of the signals. Boost.Signals allows the user to specify the manner in which multiple return values are combined.

Motivation

There are (at least) three signal implementations out there. The table below shows the most well-known implementations with some remarks.

Name Remarks
boost.signals
  • The first version of boost's solution to signals.
  • Extremely slow (see benchmarks below).
  • Not header-only.
  • Not thread-safe.
  • Works with very old compilers.
  • No disconnect callback.
boost.signals2
  • The second version of boost's solution to signals.
  • Still pretty slow (see benchmarks below).
  • Header-only.
  • Thread-safe.
  • No disconnect callback.
libsigc++
  • Independent of boost.
  • Not header-only.
  • Relatively fast.
  • Slightly "quirky" interface (signal<void,int> instead of signal<void(int)>)
  • No disconnect callback.

When you're working on a high-performance application such as a real-time game, you're often reluctant to use certain constructs (such as signals) because of performance concerns. As you can see from the table above, this might indeed be an issue with the given implementations. This has led to a fcppt.signal.

Basic usage

Here's a simple, motivating example:

#include <fcppt/make_ref.hpp>
#include <fcppt/text.hpp>
#include <fcppt/io/cout.hpp>
#include <fcppt/signal/auto_connection.hpp>
#include <fcppt/signal/object.hpp>
namespace
{
struct test_struct
{};
void
callback(
int const _value
)
{
<< FCPPT_TEXT("\"callback\" called with ")
<< _value
<< FCPPT_TEXT("!\n");
}
void
other_callback(
test_struct &
)
{
<< FCPPT_TEXT("\"other_callback\" called\n");
}
}
int
main()
{
typedef
void (int)
>
signal_type;
signal_type signal;
// Connect function "callback" to signal
signal.connect(
&callback
}
)
);
// Call the signal, will output: "callback" called with 3
signal(3);
// Define another function
typedef
void(
test_struct &
)
>
signal2_type;
signal2_type signal2;
fcppt::signal::auto_connection const connection2(
signal2.connect(
&other_callback
}
)
);
test_struct foo;
// Outputs: "other_callback" called
signal2(
foo
);
}

As you can see, a signal is represented by an object of type fcppt::signal::object. This type has a template parameter which contains the signal's function type. Note that signals may have a return type; see below for more on that.

To attach a callback function to the signal, you have to call the fcppt::signal::object::connect member function. Arbitrarily many attached functions are allowed. The connect function returns an auto_connection which is a unique pointer to a connection. The reason behind this is that connections may keep additional information that is important when they are destroyed (see Disconnect callbacks). The callback function is disconnected from the signal when the connection object dies.

Note
The "real" connection class, fcppt::signal::connection, is somewhat "implementation-defined". You're never supposed to "look inside" this object. Merely pass it around using a smart pointer to it.

fcppt::signal::object has overloads for operator(), so you can treat a signal as a function.

Signals with return values

If a signal has a return value other than void, you cannot use fcppt::signal::object's default constructor. Instead, there's a constructor that takes a function of type

result_type(result_type,result_type)

where result_type is the return type of the signal's function type. This function is used to combine the results of the different callbacks that are connected to the signal. The initial value of this operation is passed when the signal itself is called.

Here's an example which should clarify how combiners are used:

#include <fcppt/text.hpp>
#include <fcppt/io/cout.hpp>
#include <fcppt/signal/auto_connection.hpp>
#include <fcppt/signal/object.hpp>
namespace
{
int
first_callback()
{
return 4;
}
int
second_callback()
{
return 8;
}
int
third_callback()
{
return 15;
}
int
combiner(
int const a,
int const b
)
{
return a * b;
}
}
int main()
{
typedef int function();
function
> int_signal;
int_signal signal{
int_signal::combiner_function(
&combiner
)
};
fcppt::signal::auto_connection const connection1{
signal.connect(
&first_callback
}
)
};
fcppt::signal::auto_connection const connection2{
signal.connect(
&second_callback
}
)
};
fcppt::signal::auto_connection const connection3{
signal.connect(
&third_callback
}
)
};
// Outputs ((1*4)*8)*15=480
<<
signal(
int_signal::initial_value{
1
}
)
<<
FCPPT_TEXT('\n');
}

Note that there are setters for both the initial value and the combiner.

Connection managers

If you have code with a lot of connection objects, like this:

class foo
{
public:
// ...
private:
};

Then using a container of connections may be a better approach. fcppt::signal::auto_connection_container is a vector of fcppt::signal::auto_connection. Again, an example should clarify how it's used:

#include <fcppt/assign/make_container.hpp>
#include <fcppt/signal/auto_connection.hpp>
#include <fcppt/signal/auto_connection_container.hpp>
#include <fcppt/signal/object.hpp>
#include <fcppt/config/external_begin.hpp>
#include <utility>
#include <fcppt/config/external_end.hpp>
namespace
{
void
function1()
{
}
void
function2()
{
}
void
function3()
{
}
}
namespace
{
void
take_connection(
)
{
// do something with connection
}
}
int
main()
{
void ()
> void_signal;
void_signal signal;
// use fcppt::assign::make_container to pass a container of connections
>(
signal.connect(
::function1
}
),
signal.connect(
::function2
}
)
)
);
// or you can take the auto_connection and process it somehow
signal.connect(
::function3
}
)
);
::take_connection(
std::move(
connection4
)
);
}

Disconnect callbacks

One unique feature of fcppt::signal::object is the ability to receive a callback from the signal when a connection dies. To demonstrate how this works, let's say you want to associate a name with a signal. Users can then attach to such a named signal (think about a Quake-style console where you can register new console commands). However, when the last connection to a named signal dies, the signal should die with it. To do that, we define:

typedef
void(),
>
signal_type;
typedef
signal_type
>
name_to_signal;
name_to_signal global_name_to_signal;
// This function will be called whenever a connection dies. It receives the
// signal's name.
void
remove_function(
std::string const &_name
)
{
<< "Goodbye, "
<< _name
<< "!\n";
// Signal has "empty" to test if there are connections attached to it.
if(
global_name_to_signal.find(
_name
)->second.empty()
)
global_name_to_signal.erase(
_name
);
}

To receive disconnect "events", we use fcppt::signal::object's second template parameter and set it to fcppt::signal::unregister::base. Using this base class, we have the empty method at our disposal to check if there are connections attached to the signal.

To register to a named signal, we define the following function:

register_named_signal(
std::string const &_name,
)
{
// If the signal doesn't exist, add it to the map.
if(
global_name_to_signal.find(
_name
)
==
global_name_to_signal.end()
)
global_name_to_signal.insert(
std::make_pair(
_name,
signal_type()
)
);
return
global_name_to_signal.find(
_name
)->second.connect(
_function,
// Add our remove function as the disconnect handler (see above)
&remove_function,
_name
)
}
);
}

Note that the remove callbacks' signature is just void(). Our main might look like this:

int
main()
{
fcppt::signal::auto_connection const hello_connection(
register_named_signal(
"hello",
[]{
std::cout << "hello!\n";
}
}
)
);
// Will print "goodbye"
global_name_to_signal.find(
"hello"
)->second();
{
fcppt::signal::auto_connection const goodbye_connection(
register_named_signal(
"goodbye",
[]{
std::cout << "goodbye!\n";
}
}
)
);
// Will print "goodbye!"
global_name_to_signal.find(
"goodbye"
)->second();
// Will print "Goodbye goodbye!" at end of scope
}
// Will print "Goodbye hello!" at end of scope
}

Note that in "real" code, it is advisable to use more typedefs: Use a typedef for the signal's function, one for the signal itself and one for the signal's function wrapper (std::function<void()>).

Benchmarks

We're assuming connecting and disconnecting signals isn't a big performance issue. Instead, our benchmark is as follows:

The results are as follows:

Signals-Implementation gcc-4.6 (-O3) clang-3.0 (-O3) icc-12.1.0 (-O3)
boost.signals 1.426s 1.444s 1.679s
boost.signals2 0.871s 1.644s 1.728s
libsigc++ 0.614s 0.615s 0.647s
fcppt.signal 0.526s 0.505s 0.539s

Or, graphically:

signal_benchmark.svg
Benchmark results

As you can see, fcppt.signal outperforms all the other signal libraries. Additionally, it behaves the same way across different compilers with respect to speed.

Classes

class  fcppt::signal::base< T >
 Default base class for signals. Provides no unlinking capabilities. More...
 
class  fcppt::signal::connection
 A connection returned by a connect call. More...
 
class  fcppt::signal::object< T, Base, Enable >
 Represents a signal with a non-void return value. More...
 
class  fcppt::signal::unregister::base< T >
 A base class for signals providing unlinking. More...
 

Typedefs

typedef fcppt::unique_ptr< fcppt::signal::connectionfcppt::signal::auto_connection
 A unique pointer to a connection. More...
 
typedef fcppt::optional::object< fcppt::signal::auto_connectionfcppt::signal::optional_auto_connection
 An optional auto connection. More...
 
typedef fcppt::function< void()> fcppt::signal::unregister::function
 The unregister function object. More...
 

Typedef Documentation

◆ auto_connection

A unique pointer to a connection.

◆ function

The unregister function object.

◆ optional_auto_connection

An optional auto connection.