This is a comparative article on two famous C Signal-Slot libraries Boost.Signals and Libsigc . Boost.Signals is part of the famous Boost library; libsigc is used by GTK / GNOME's official C version of GTKMM / GNOMEMM. The original location of this article is: http://www.3asonc.com/opensource/boost.bind-vs-sigc2.html
Boost.Signals vs Libsigc
This document is an attempt to perform a detailed comparison of two implementations of what is widely known as a Signal / Slot library. The two libraries in question are libsigc (v2.x) and Boost.Signals. One of the stated goals of this comparison is to provide some initial analysis as a precursor to submission to the C standards body for inclusion in the standard library. libsigc was written / maintained by a succession of people including Tero Pulkkinen, Karl Nelson, Murray Cumming and Martin Schulze. Boost.Signals was Dougo. Gregor and strongly inflow by the work of karl Nelson.
IN FACT, IT WAS DOUGLAS GREGOR WHO ASKED for a Comparison, I am Merely The Person Who Picked Up The Gauntlet [-ed Such a Pretty Little Gauntlet, It'Ll Look Nice In My Collection, TOO BAD IT'S MISSING A Finger].
THIS Document Will Be Split Into Two Parts. The First Being A Straightforward Comparison of The Features of Each Library and the interface Which IMPLEments Those Features.
The second part will be a code comparison of the features of each library, doing an identical task. This will facilitate performance and executable size comparisons. [-Ed Just saw Jonathan Brandmeyer's code post, and also Chris Vine's boost example. Will include those shortly ]
In this early draft form, I will not hesitate to provide subjective evaluation from a personal point of view, speaking as a user who (at this point) really has not used either of the libraries. I have used the earlier v1.2 version of libsigc , and I've also used a much earlier library called "Callback" written by Rich Hickey about 8yrs ago. I'll try to mark those views with [--ed (itorial)] marks, just so everyone's clear which bits can be tossed from the final draft Discussion's gotta start somewhere.WARNING:. I have not actually compiled any of the code examples Many were taken straight from the various tutorials and docs from each library However, there's no guarantee (at this point.. That They is.
FEATURES
Both libraries aim to provide Signal / Slot functionality, where a Signal can be loosely described as a publisher, with Slot objects as subscribers. Each library implements this as a one-to-many relationship, with each Signal capable of communicating with an arbitrary number Of Slots.
The Slot objects are basically functor objects which forward an invocation to the destination function. Each library has the capability to connect to functor objects, class member functions or standalone functions.
Note, for analysis purposes, I'll use the "best" interface for each library, which means Preferred Syntax for Boost.Signals, and the simplified wrapper interfaces for libsigc . See http://www.boost.org/doc/html /ch06s02.html for more discussion.
[-ed the preferred syntax for boost.signals Seems Quite Nice, and I Wonder How Difficult It Would Be To Add THIS TYPE OF Wrapper To the libsigc library.]
Signal Declarations
Both Libraries Allow Signals To Be Declared for An Arbitrary (But Not Unlimited) Number of Parameters. Each Library Also Allows The Return Value Type To Be Specified.
Boost.Signalslibsigc // Signal with two args and int return // preferred usageboost :: signal
As you can see, the two libraries are very similar when declaring a signal. The main difference being that Boost.Signals allows the use of a complete function signature as the template parameter, instead of listing the return / argument types individually as in libsigc .
Slot Object
Both libraries implement a Slot object to manage the subscriber relationship and to encapsulate the differences between functor, function pointer, and member function pointer Boost.Signals implements slots in such a way so that code bloat is minimized and efficiency is maximized (see http.: //www.boost.org/doc/html/ch06s06.html).
This seems also to be the case with libsigc slot objects, which are also templatized. [-Ed without looking too hard at the code, the pattern of implementation (ie concrete base class with pimpl idiom, derived template classes) seems the same, so I'm Guessing They Both Produce Similar Bloat Factory. SomeOne Correct Me ??]
When connecting slots to signals, libsigc has a more explicit syntax, requiring the use of sigc :: mem_fun () or sigc :: ptr_fun () to construct the appropriate slot object. [-Ed I'm a bit unclear about functor objects, IT Seem Thei Can Just Be Passed in The Sigc :: Signal :: Connect () Function ??]
Boost.Signals is able to figure out the proper type of slot to construct based on the parameters to the boost :: signal :: connect () function, as long as it's a functor object or a ptr-to-function. If it's a MEMBER FUNCTION, BOOST :: Bind () Must be used to bind the class object to the class-member-function ptr.common code int function; class foo {public: // ... int func (FLOAT VAL, STRING STR);}; Float Val, String Str);}; foo obj; Boost.Signalslibsigc // Signal with Two args and int returnboost :: Signal
[-Ed I prefer libsigc notation of explicitly using mem_fun () and ptr_fun (), as it seems to be a bit more consistent in notation. It also nicely follows the existing C standard naming convention for this type of functionality.]
Todo: Answer Questions: a) CAN Slots BE Used Independently from Signals As Functors Hiding The Actual Implement B) CAN Libsigc Take Plain Functor Object As Parameter To Connect ()
Slot Binders
Boost.Signals Uses The Boost.Bind Library, Which Province A Pletra of Useful Features. [-Ed I'D Love this, As long as iln't Have to Use All of Boost to get it].
. Libsigc library also provides many features for binding or otherwise transforming function calls It does so with a few different classes, each of which performs a specific binding task.A table of equivalent functionality follows:
Boost.Bind Sigc :: Bind (), Sigc :: Compose (), Sigc :: Hide (), Sigc :: bind_return (), Sigc :: HIDE_RETURN (), Sigc :: EXCEPTION_CATCH () no clue Sigc :: Retype (), Sigc :: RetyPE_RETURN ()
TODO: ANSWER Questions: a) What's up with the no clue entry, anyone?
Connection Management
Both Libraries Provide Automatic (Via DeriVation) and Explicit Connection Lifetime Management. Both Libraries Also Provide a Way to Check if The Connection Is Valid.
Explicit Disconnection
Both libraries provide a method for Slots to be explicitly disconnected from the signal. Both libraries provide this in the form of a connection class which is returned when a slot is connected to the signal. This connection class can be used to check the status of the Connection, and to disconnect the slot from the signal.
Common code int func1 (Float Val, String Str); Boost.SignalSlibsigc // Signal With Two args and int Returnboost :: Signal
Both libraries provide a method for Slots to be automatically disconnected from the signal when the object the Slot calls is destructed. Both libraries provide this in the form of a helper class to use as a public base class for the connected class in question. Each library Even names it Similarly.
Boost.Signalslibsigc struct Functee: public boost :: signals :: trackable {int operator () (float val, string str);}; Functee * f = new Functee (); Functee * g = new Functee (); // Signal WITH TWO ARGS AND INT RETURNBOOST :: Signal
Additional Connection Management Features
Additional Functionality Can Be Found In The Libsigc Library, In The From Of ALLOWING SLOTS to Be Temporarily Blocked. See Example Below:
Libsigc Int Func1 (FLOAT VAL, STRING STR); Int Func2 (Flunc2 (Flunc2 (Flunc2 (Flunc2 (Signal With Two Args and Int Return Valuesigc :: Signal
There are two special issues associated with the one-to-many signal / slot relation, especially in the context of multiple functions and return values. The first issue is what to do with the return values from multiple Slots, and the second issue is the ORDERING OF THE Slot Callbacks.
Both Libraries Provide Methods To DEAL with these Issues, But Those Methods Are Where We See The First Real Differences Between The Library Implementations.
Return Values
Both libraries provide ways to manage the return values of the slots. In fact, the interface of each seems pretty much identical, minus some cosmetics. In fact, in the following code block, the aggregate_values struct comes directly from the Boost.Signals tutorial, But is usable with the libsigc library as well.
Common code int func1 (FLOAT VAL, STRING STR); INT FUNC2 (FLUNC2 (FLOAT VAL, STRING STR); Template
Container Operator () (Inputiterator First, InputItemrator Last) Const
{
Return Container (First, Last);
}
}
Boost.Signalslibsigc // Signal with two args and int return valueboost :: signal
This is the one area where the two libraries differ significantly. Libsigc provides access to the list of slots via an STL-like slot_list interface, while Boost.Signals hides the storage implementation and only provides a coarse ordering.
Since libsigc provides access to the list of connected slots, it is a simple matter to insert a Slot anywhere in the list (given an iterator). In addition, normal push_front () and push_back () operations are available, as well as forward and Reverse itrators. by Default, Slots Are Pushed ONTO The End of The Signal's Slot List.
Boost.Signals provides ordering via the Group and GroupCompare template parameters. Default settings use Group = int and GroupCompare = std :: less
Common Code Int Func1 (Float Val, String Str); Int Func2 (Flunc2 (Flunc2 (Flunc2 (Flunc2 (FLOAT VAL, STRING STR); Boost.Signalslibsigc // Signal With Two Args and int Return Valueboost :: Signal
The Boost.Signals Library IS Capable of Disconnecting All Signals of a Certain Group. This is Done Via the Boost :: Signals :: Signal :: Disconnect (const group_type&) Function:
Boost.Signals Int Func1 (Float Val, String Str); Int Func2 (Flunc2 (Flunc2 (Float Val, String Str); // Signal with Two Args and Int Return Valueboost :: Signal
SIG;
Boost :: Signals :: Connection C = Sig.Connect (1, & Func1);
Sig.Connect (0, & func2);
SIG (3.1415926, "Pi"); // Func2 (), THEN FUNC1 ()
Sig.disconnect (1);
SIG (1.414141414, "SQRT (2)"); // only func2 ()
Some developers might like Boost.Signal's handling of slot groups, since it makes it easy to disconnect a subset of slots without having to keep track of the individual connection objects. Similar functionality could be achieved via the libsigc library by groups via implementing subsignals. For EXAMPLE:
libsigc int Func1 (float val, string str); int Func2 (float val, string str); typedef sigc :: signal
Libsigc Template
Container Operator () (Inputiterator First, InputItemrator Last) Const
{
Return Container (First, Last);
}
}
Template
Struct aggregate_aggregates
{
TypedEf Container Result_Type
Template
Container Operator () (Inputiterator First, InputItemrator Last) Const
{
Container Result;
For (INPUTITERATOR IT = First; It! = Last; IT) {
Copy (it-> begin (), it-> end (), back_inserter (result));
}
Return Result;
}
}
Typedef aggregate_aggregates
Typedef aggregate_values
TypedEf Sigc :: Signal
// Signal with Two Args and int Return Value
Mysig sig;
Mysig * group1 = new mysubsig ();
Mysig * group2 = new mysubsig ();
Sig.connect (group1-> make_slot ());
Sig.connect (group2-> make_slot ());
Group1-> Connect (Sigc :: Ptr_Fun (& Func1));
Group2-> Connect (Sigc :: PTR_FUN (& FUNC2));
Std :: Vector
Delete group1;
Std :: Vector
As you can see, this gets quite involved, compared to boost.signals.
General Notes
Return Value AGGREGATION
libsigc and Boost.Signals both provide a way to combine the return values of multiple slots. Both libraries provide this functionality by way of a template parameter, allowing end developers to provide their own aggregation objects.
The difference is in default values. Boost.Signals provides the last_value <> template, which iterates through the slot list and returns the value of the last slot. In comparison, the default T_accumulator type in libsigc signal definition is 'nil', which triggers .
[-Ed without looking at the code] In preparation for any standardization, I think it would be important to do some real benchmarking to see if the libsigc specialization code is really worth the effort in terms of performance.
On the surface, Boost.Signals seems to be the better method, and seems to be an effective application of the principles shown in Alexandrescu's "Modern C Design" book. In terms of code, it certainly seems better to defer the specialization to a small Parameterized Class Than Maintain A Whole Separate Signal Implementation Based ON
Boost.Signals is at a bit of a disadvantage because the trackable class does not work with Boost.Lambda and Boost.Function. In this bsense, libsigc library's implementation seems superior, if only because you do not have to think about limitations.
[-ED Personal Opinion Here ...
Function binding
The functionality provided by the Boost.Bind library is equivalent to a subset of the libsigc Adaptor classes. However, I like the approach of Boost.Bind, since it subsumes quite a bit of functionality under one interface. Granted the interface is more subtle in Boost.Bind, whereas libsigc 's interface to the same feature set is quite explicit My personal preference is for Boost.Bind, because at least to me it just seems to "do the right thing" TODO:.. implement sigc :: bind_return () Sigc :: hide_return (), sigc :: exception_catch () in terms of boost.bind. --ed done]
Credits
Special thanks to everyone who read this and contributed. Thanks to Edward Diener for Boost.Signals corrections. Thanks to Murray Cumming for general libsigc corrections. Thanks to Jonathan Brandmeyer who implemented the code demonstrations suggested by Murray Cumming. Thanks to Chris Vine who wrote a Comprehensive boost.signals demo. And of Course to Douglas Gregor, Who Seems to Have Misplace His Gauntlet.