分享
 
 
 

Boost.Signals vs libsigc++

王朝c/c++·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

这是一篇关于两个著名的C++ signal-slot库 Boost.Signals 和 libsigc++ 的比较文章。

Boost.Signals是著名的Boost库的一部分;libsigc++则被gtk+/gnome的官方C++版本gtkmm/gnomemm所使用。

这篇文章的原始位置在:http://www.3sinc.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 developed by Douglas Gregor and strongly influenced 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 won't hesitate to provide subjective evaluation from a personal point of view, speaking as a user who (at this point) really hasn't 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 are correct in any meaningful way.

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.Signals

libsigc++

// Signal with two args and int return

// preferred usage

boost::signal<int (float, string)> sig;

sig(3.1415926, "pi");

// Signal with two args and int return

// preferred usage

sigc::signal<int, float, string> sig;

sig(3.1415926, "pi");

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 (i.e. concrete base class with pimpl idiom, derived template classes) seems the same, so I'm guessing they both produce similar bloat factors. 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 seems they 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 Func(float val, string str);

class Foo {

public:

// ...

int Func(float val, string str);

};

struct Bar {

int operator()(float val, string str);

};

Foo obj;

Boost.Signals

libsigc++

// Signal with two args and int return

boost::signal<int (float, string)> sig;

sig.connect(Bar());

sig.connect(&Func);

sig.connect(boost::bind(&Foo::Func, obj, _1, _2));

sig(3.1415926, "pi");

// Signal with two args and int return value

sigc::signal<int, float, string> sig;

sig.connect(Bar());

sig.connect(sigc::ptr_fun(&Func));

sig.connect(sigc::mem_fun(obj, &Foo::Func));

sig(3.1415926, "pi");

[-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 implementation b) can libsigc++ take plain functor object as parameter to connect()

Slot Binders

Boost.Signals uses the Boost.Bind library, which provides a plethora of useful features. [-ed I'd love this, as long as I didn'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.Signals

libsigc++

Boost.Bind

sigc::bind(), sigc::compose(), sigc::hide(), sigc::group()

No Equivalent

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);

int Func2(float val, string str);

Boost.Signals

libsigc++

// Signal with two args and int return

boost::signal<int (float, string)> sig;

boost::signals::connection c =

sig.connect(&Func1);

sig.connect(&Func2);

// Func1(), then Func2()

sig(3.1415926, "pi");

c.disconnect();

// only Func2()

sig(1.414141414, "sqrt(2)");

assert(!c.connected());

// Signal with two args and int return value

sigc::signal<int, float, string> sig;

sigc::connection c =

sig.connect(sigc::ptr_fun(&Func1));

sig.connect(sigc::ptr_fun(&Func2));

// Func1(), then Func2()

sig.emit(3.1415926, "pi");

c.disconnect();

// only Func2()

sig(1.414141414, "sqrt(2)");

assert(!c.connected());

Automatic disconnection

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.Signals

libsigc++

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 return

boost::signal<int (float, string)> sig;

sig.connect(*f);

sig.connect(*g);

// f::operator()(), then g::operator()()

sig(3.1415926, "pi");

delete g;

// only f::operator()()

sig(1.414141414, "sqrt(2)");

struct Functee :

public sigc::trackable {

int operator()(float val, string str);

};

Functee* f = new Functee();

Functee* g = new Functee();

// Signal with two args and int return

sigc::signal<int, float, string> sig;

sig.connect(sigc::ptr_fun(f, &Functee::operator()));

sig.connect(sigc::ptr_fun(g, &Functee::operator()));

// f::operator()(), then g::operator()()

sig.emit(3.1415926, "pi");

delete g;

// only f::operator()()

sig(1.414141414, "sqrt(2)");

NOTE: Boost.Signals documentation states that there are exceptions to using boost::signals::trackable, specifically the trackable base class is unable to manage connections created using Boost.Function or Boost.Lambda. This problem is stated to be resolved in future versions.

Additional Connection Management Features

Additional functionality can be found in the libsigc++ library, in the from of API allowing Slots to be temporarily blocked. See example below:

libsigc++

int Func1(float val, string str);

int Func2(float val, string str);

// Signal with two args and int return value

sigc::signal<int, float, string> sig;

sigc::connection c = sig.connect(sigc::ptr_fun(&Func1));

sig.connect(sigc::ptr_fun(&Func2));

sig.emit(3.1415926, "pi"); // Func1(), then Func2()

c.block(); // temporarily blocked

sig(1.414141414, "sqrt(2)"); // only Func2()

c.unblock(); // back to normal

sig.emit(3.1415926, "pi"); // Func1(), then Func2()

c.disconnect();

sig(1.414141414, "sqrt(2)"); // only Func2()

Advanced Signal Management

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(float val, string str);

template<typename Container>

struct aggregate_values

{

typedef Container result_type;

template

Container operator()(InputIterator first, InputIterator last) const

{

return Container(first, last);

}

};

Boost.Signals

libsigc++

// Signal with two args and int return value

boost::signal<int( float, string),

aggregate_values<std::vector<int> > > sig;

sig.connect(&Func1);

sig.connect(&Func2);

// Func1(), then Func2()

std::vector<int> res = sig(3.1415926, "pi");

// Signal with two args and int return value

sigc::signal<int, float, string,

aggregate_values<std::vector<int> > > sig;

sig.connect(sigc::ptr_fun(&Func1));

sig.connect(sigc::ptr_fun(&Func2));

// Func1(), then Func2()

std::vector<int> res = sig(3.1415926, "pi");

Slot Ordering

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 iterators. 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<Group>, which by default implements a simple integer-based ordering scheme. The desired group can be specified at connect() time, allowing a coarse ordering of slots. Once a slot is connected, there is no interface available for reordering slots.

Common Code

int Func1(float val, string str);

int Func2(float val, string str);

Boost.Signals

libsigc++

// Signal with two args and int return value

boost::signal<int (float, string)> sig;

sig.connect(1, &Func1);

sig.connect(0, &Func2);

sig(3.1415926, "pi"); // Func2(), then Func1()

// Signal with two args and int return value

sigc::signal<int, float, string> sig;

sig.connect(sigc::ptr_fun(&Func1));

sig.slots().push_front(sigc::ptr_fun(&Func2));

sig(3.1415926, "pi"); // Func2(), then Func1()

Both libraries will execute the slots in the order in which they were connected [well, to be clear, Boost.Signals will use FIFO ordering in the next release, RSN --Edward Diener].

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(float val, string str);

// Signal with two args and int return value

boost::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 implementing groups via subsignals. For example:

libsigc++

int Func1(float val, string str);

int Func2(float val, string str);

typedef sigc::signal<int, float, string> MySig;

// Signal with two args and int return value

MySig sig;

MySig* group1 = new MySig();

MySig* group2 = new MySig();

sig.connect(group1->make_slot());

sig.connect(group2->make_slot());

group1->connect(sigc::ptr_fun(&Func1));

group2->connect(sigc::ptr_fun(&Func2));

std::vector<int> res = sig(3.1415926, "pi"); // Func1(), then Func2()

delete group1;

std::vector<int> res = sig(3.1415926, "pi"); // just Func2()

NOTE: This scheme will not work if the accumulator template parameter is used, since the accumulator changes the effective return value of the master signal, but the subsignals will continue to return only the value of the last slot called. It may be possible to work around this, but that would require two accumulator classes because of the varying return values at each signal level. For example:

libsigc++

template<typename Container>

struct aggregate_values

{

typedef Container result_type;

template

Container operator()(InputIterator first, InputIterator last) const

{

return Container(first, last);

}

};

template<typename Container>

struct aggregate_aggregates

{

typedef Container result_type;

template

Container operator()(InputIterator first, InputIterator last) const

{

Container result;

for(InputIterator it = first; it != last; ++it){

copy(it->begin(), it->end(), back_inserter(result));

}

return result;

}

};

typedef aggregate_aggregates<std::vector<int> > MyAgAg;

typedef aggregate_values<std::vector<int> > MyAgVal;

typedef sigc::signal<MyAgVal::result_type, float, string, MyAgAg> MySig;

typedef sigc::signal<int, float, string, MyAgVal> MySubSig;

// 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<int> res = sig(3.1415926, "pi"); // Func1(), then Func2()

delete group1;

std::vector<int> res = sig(3.1415926, "pi"); // just Func2()

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 template specializations. The libsigc++ documentation states that this is done for efficiency reasons.

[-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 <T_accumulator=nil>.

Trackable

Boost.Signals is at a bit of a disadvantage because the trackable class doesn't work with Boost.Lambda and Boost.Function. In this bsense, libsigc++ library's implementation seems superior, if only because you don't 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 misplaced his gauntlet.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有