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.

Filename Size (bytes) Date (Y.M.D)
libptsigslot-20071119.tar.gz 54497 2007.11.19
libptsigslot-20071111.tar.gz 56449 2007.11.11

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.