Local projects: - editline - jQuery plugins - readline_cpp - parse0x - ptsigslot - shellish - sqlite3x/sq3 - Tech papers - yo5 - zfstream Remote projects: - s11n.net - SpiderApe - toc Stuff for...: - gcc - GNU make - JavaScript - jQuery - QEMU - sqlite3
| ptsigslot signals/slots library for C++
ptsigslot is a "signals and slots" library derived from the
Platinum C++ Framework
(a.k.a. Pt). This implementation is functionally the same as that one but was separated
to turn it into a standalone sigslot library.
"Signals and slots" is an idiom used in C++ for delivering messages/events between
disparate objects which have no inherently knowledge of each other. In this model,
"signal" objects connect to "slot" objects. When a signal is triggered, all slots
connected to it are passed on the arguments which were passed to the signal.
This approach to message passing is used extensively in the popular
Qt framework, and several
good C++ implementations exist (e.g.
Boost.Signals
and libsigc++),
each with their own list of pros and cons. Here's another one...
In short, this library provides the ability to connect signals to free
functions and member functions (including const and static members).
It does so with compile-time type safety and it also tracks the lifetimes
of connected objects so that everyone is properly informed when a
signal or a slot is destructed. (Note that the lifetime tracking is
necessarily intrustive - it requires subclassing a type provided by
this library.)
This distribution is intended to be copied into local projects, rather
than be installed as a system-wide library. The code has no
3rd-party code dependencies except for the STL. See the INSTALL file
for more details.
License:
This code is released under the GNU LGPL version 2, and is thus freely
usable in commercial and open projects. In short, changes you make
to this code must be released under the LGPL, but linking to
this library does not affect the license of any linked client
code (this is in stark contrast to the normal GPL).
Notable features
Each signals/slots implementation has its own list of notable features. The
feature list for this implementation includes:
- Supports binding member functions, including const and static members, and free
functions as slots (all using the same API).
- Supports lifetime tracking of connected objects. For example, if a slot object
is destroyed then calling connected signals won't cause a segfault.
- No 3rd-party dependencies (requires only the STL).
- Fairly small and structured to be easy to copy directly into
arbitrary project trees. (While it is 95% header-only template code, in includes
two small .cpp files which must be linked into your project or compiled
to a static or dynamic library.)
- Should compile on a rather large range of platforms (see below for more detail).
History and acknoledgements
This code was written 99% by Dr. Marc Duerner for his
Pt project.
A colleague of his,
Tommi Mäkitalo of tntnet.org,
split off a copy of the code for use in a larger STL-based project. Later on, i
split out that copy and used it as a basis for this standalone version
of the library. The only significant differences between this
distribution and the others are: 1) this one is a standalone library
(no other dependencies and no support for features outside the scope
of signals and slots). 2) This code is generated by a Perl script,
which is used to generate the template guts necessary to support a
varying number of signal/slot arguments. In turn, the code generator
used to create most of this code is now used to generate the code for
the Pt project's edition of this library. Funny how that worked out,
really.
Supported Platforms:
While the included Makefile is specific to GNU platforms hosting gcc,
the code will, in theory, compile on nearly any platform supporting a recent C++
compiler. Marc has spent an enormous amount of effort ensuring that his code compiles
on AIX, common Unix variants, WinCE, and several other strange systems (e.g. QNX
and something called XLC). That said, i believe it may be problematic on some
platforms which do not allow an explicit return of a void value, as demonstrated
here:
void bar();
void foo() { return bar(); /* <-- some compilers may complain about the return here. */ }
If your compiler is affected by that problem, then tough luck - fixing
it requires adding void-specific specializations of all sorts
of template code and i'm not willing to go back and coddle deficient
complilers (if you are willing to, then by all means send me a patch).
Download the Source Code:
The source code includes a GNU Makefile which "should work" on any
systems hosting GNU Make and gcc. It will not work with other Makes
but should be easily portable to other compilers. It has no configure
script, so just run 'make' to build it. To use it in your own projects,
see the INSTALL file for info on what files you will need.
Documentation:
None yet. That honestly is on the TODO list.
You can find an introduction to this API in
the Pt documentation.
Since this code is derived directly from that project, that document is completely applicable to this code.
Example:
The following example demonstrates several things:
- The use of the Connectable class to implement safe connection tracking. When a Connectable
object destructs it is removed from all listeners. (In fact, any object which connects
to a signal (except for free functions) has to publically derive from Connectable.)
- The use of inner Connectable classes for cases where an enclosing class cannot be made
polymorphic.
- The ability to tie free functions and member functions (including const and static members)
to signals.
- Delegates, which are essentially Signals with only a single target Slot but which can return
the value of their called slot.
Don't be put off by the length of the example - using the library is really simple and this example
demonstrates everything you need in order to use it.
#include "ptsigslot/signals.hpp"
#include <iostream>
#define COUT std::cout << __FILE__ << ":" << std::dec<<__LINE__ << ": "
int function()
{
COUT << "function() called." << std::endl;
return 0;
}
int function1i(int i)
{
COUT << "function(int) called: "<<i<<"" << std::endl;
return 0;
}
struct Object : public ptsigslot::Connectable
{
bool method()
{
COUT << "Object::method called." << std::endl;
return true;
}
bool method1( std::string const & m )
{
COUT << "Object::method(string) called: " << m << std::endl;
return true;
}
char constMethod() const
{
COUT << "Object::constMethod called." << std::endl;
return 'r';
}
void constMethod2(int,int) const
{
return;
}
static std::string staticMethod()
{
COUT << "Object::staticMethod called." << std::endl;
return "test";
}
};
/**
If you have a class which you can edit but you do not want to
subclass ptsigslot::Connectable, a partial workaround is to define
an inner class which subclasses Connectable, then use it to proxy
signals through to the non-virtual containing type. That approach
is demonstrated below...
*/
class NonSlotted
{
public:
struct SlotProxy : public ptsigslot::Connectable
{
NonSlotted & self;
SlotProxy( NonSlotted & n ) : self(n)
{
}
double proxy( double d )
{
COUT << "NonSlotted::SlotProxy::proxy("<<d<<")"<<std::endl;
return self.proxied(d);
}
int slotish()
{
COUT << "NonSlotted::SlotProxy::slotish()"<<std::endl;
return self.slotish();
}
};
NonSlotted() : m_proxy(*this)
{
}
double proxied(double d)
{
COUT << "NonSlotted::proxied("<<d<<")"<<std::endl;
return 2*d;
}
SlotProxy & proxy()
{
return this->m_proxy;
}
int slotish()
{
COUT << "NonSlotted::slotish())"<<std::endl;
return 42;
}
private:
SlotProxy m_proxy;
};
#include <ptsigslot/delegate.hpp>
void delegate_tests()
{
COUT << "Delegates...\n";
namespace ps = ptsigslot;
if( 1 )
{
ps::Delegate<int> d1;
ps::connect( d1, function );
d1();
NonSlotted obj;
ps::connect( d1, obj.proxy(), &NonSlotted::SlotProxy::slotish );
d1();
}
if( 1 )
{
Object obj;
ps::Delegate<bool> db;
ps::connect( db, obj,&Object::method );
std::cout << "db() == " << db() << std::endl;
}
if( 1 )
{
bool thrown = false;
try
{
Object * obj = new Object;
ps::Delegate<bool> db;
ps::connect( db, *obj, &Object::method );
db();
delete obj;
COUT << "db() should throw here:\n";
db();
}
catch( std::exception const & ex )
{
thrown = true;
COUT << "EXPECTED exception: " << ex.what() << std::endl;
}
if( ! thrown )
{
throw std::runtime_error("delegate_tests() expected an exception and got none.");
}
}
COUT << "Delegate tests done.\n";
}
int main(int argc, char* argv[])
{
namespace ps = ptsigslot;
try
{
Object obj;
ps::Signal<> signal;
signal.connect( ps::slot(obj, &Object::method) );
signal.connect( ps::slot(obj, &Object::constMethod) );
signal.connect( ps::slot(&Object::staticMethod) );
signal.connect( ps::slot(function) );
signal.send();
Object * o2 = new Object;
ps::connect( signal, *o2, &Object::method );
COUT << "before (delete o2): signal():\n";
signal();
delete o2;
COUT << "after (delete o2): signal():" << std::endl;
signal();
ps::Signal<int> intsig;
intsig.connect( ps::slot(function1i) );
intsig( 42 );
ps::Signal<std::string const &> strsig;
strsig.connect( ps::slot(obj, &Object::method1) );
strsig( "Hi, world" );
ps::Signal<std::string const &> strcp(strsig);
strcp("Bye, world");
ps::Signal<int,int> sigi2;
sigi2.connect( ps::slot( obj, &Object::constMethod2 ) );
sigi2(34,43);
NonSlotted nonslotted;
ps::Signal<double> proxiedsig;
proxiedsig.connect( ps::slot( nonslotted.proxy(), &NonSlotted::SlotProxy::proxy ) );
proxiedsig(42.42);
delegate_tests();
}
catch (const std::exception& e)
{
std::cerr << "EXCEPTION: " << e.what() << std::endl;
return 1;
}
}
|
The output looks like (line numbers may be different than above):
test.cpp:21: Object::method called.
test.cpp:33: Object::constMethod called.
test.cpp:44: Object::staticMethod called.
test.cpp:7: function() called.
test.cpp:167: before (delete o2): signal():
test.cpp:21: Object::method called.
test.cpp:33: Object::constMethod called.
test.cpp:44: Object::staticMethod called.
test.cpp:7: function() called.
test.cpp:21: Object::method called.
test.cpp:170: after (delete o2): signal():
test.cpp:21: Object::method called.
test.cpp:33: Object::constMethod called.
test.cpp:44: Object::staticMethod called.
test.cpp:7: function() called.
test.cpp:13: function(int) called: 42
test.cpp:27: Object::method(string) called: Hi, world
test.cpp:27: Object::method(string) called: Bye, world
test.cpp:67: NonSlotted::SlotProxy::proxy(42.42)
test.cpp:83: NonSlotted::proxied(42.42)
test.cpp:106: Delegates...
test.cpp:7: function() called.
test.cpp:72: NonSlotted::SlotProxy::slotish()
test.cpp:94: NonSlotted::slotish())
test.cpp:21: Object::method called.
db() == 1
test.cpp:21: Object::method called.
test.cpp:136: db() should throw here:
test.cpp:142: EXPECTED exception: Delegate not connected
test.cpp:149: Delegate tests done.
|
|