Basic examples

In these examples, assume that appropriate headers have been included, and using namespace msignals directive has been issued.

There are two syntaxes to declare a signal: normal syntax and compatibility syntax.

The normal syntax:

            signal< void () > mySignal;
            signal< void ( ArgumentType1, ArgumentType2, ... ) > mySignal;

The compatibility syntax:

            signal_0 mySignal0;
            signal_1< ArgumentType1 > mySignal1;
            signal_2< ArgumentType1, ArgumentType2 > mySignal2;
            ...

Your compiler must support partial template specialization to be able to compile the normal syntax. Use the compatibility syntax otherwise.

Now, we proceed to a simple example that shows the basic usage of the library. Suppose you have the following classes:

            class Console
            {
            public:
                void onPrintError ( const std::string& msg );
                void onPrintWarning ( const std::string& msg );
                // ...
            };

            class Compiler
            {
            public:
                void compile ( const std::string& sourcePath );

                signal< void ( const std::string& ) > sigError; 
                signal< void ( const std::string& ) > sigWarning;

                // ...
            };

            void Compiler::compile ( const std::string& sourcePath )
            {
                // ...

                std::string strError = ...; // generate error text
                sigError ( strError );

                std::string strWarning = ...; // generate warning text
                sigWarning ( strWarning );

                // ...
            }

Creating connections
To establish a communication channel between the Compiler and the Console, write something like that:

            Console console;
            Compiler compiler;

            compiler.sigError.connect< Console, & Console::onPrintError >( console );
            compiler.sigWarning.connect< Console, & Console::onPrintWarning >( console );

            compiler.compile();

Now the Console will receive onPrintError and onPrintWarning method calls.

Removing connections manually
But what about object destruction? In this case, compiler will be destroyed before the console, so there is no problem. But if we swapped the declarations, as follows:

            Compiler compiler;
            Console console;

then we'd have a problem, with dangling connections from the compiler object to the already deleted console object. To exclude this possibility we can use explicit disconnect feature. Here we have three choices:

The first one is to derive Console from msignals::target base class:

            class Console : public target
            {
                // ...
            };

Now we can use the following construct to remove the connection to the console object:

            compiler.sigError.disconnect ( & console );

In this case it's safe to pass a pointer to any class derived from Console.

What about the situation when we can't modify the inheritance hierarchy? We can still use the above syntax, but we must be aware of the fact that the pointer will be converted to void* and compared by value. This means that we must pass a pointer to the Console class, but not any derived class (since the connection won't be removed in that case). Using disconnect without deriving from target is the second possibility. It's somewhat dangerous, so be careful.

The third possibility is to use a connection handle:

            connection conErr = 
                compiler.sigError.connect< Console, & Console::onPrintError >( console );
            connection conWarn = 
                compiler.sigWarning.connect< Console, & Console::onPrintWarning >( console );

            // ...

            disconnect ( conErr );
            disconnect ( conWarn );

Here we use disconnect as a free function, not a method. Since the connection does remember both signal and slot, we don't need to specify the signal.

We can also remove all connections from particular signal:

            compiler.sigError.disconnect_all();

Removing connections automatically
All disconnect variants are useful when we explicitly track all signals that particular slot has been connected to. But it's not always the case. In case if we want msignals to track these connection automatically, we can use the managed_target base class instead of plain target:

            class Console : public managed_target
            {
                // ...
            };

Now we don't need to do any bookkeeping, as all connections established to the Console slots will be automatically removed just before the object is deleted.

One-to-many connections
Suppose now that we have acces to the interface ILogFile, compatible with the Console:

            struct ILogFile
            {
                virtual void onPrintError ( const std::string& msg ) = 0;
                virtual void onPrintWarning ( const std::string& msg ) = 0;
                // ...
            };

Given the Console instance and ILogFile interface pointer, we can connect them both:

            Console console;
            Compiler compiler;
            ILogFile* pLogFile = ...;

            compiler.sigError.connect< Console, & Console::onPrintError >( console );
            compiler.sigWarning.connect< Console, & Console::onPrintWarning >( console );

            compiler.sigError.connect< ILogFile, & ILogFile::onPrintError >( *pLogFile );
            compiler.sigWarning.connect< ILogFile, & ILogFile::onPrintWarning >( *pLogFile );

            compiler.compile();

Here the Console functions will be called before the ILogFile ones. But what if we want the reverse order (first ILogFile, then Console)? We can use another form of connect:

            compiler.sigError.connect_first< ILogFile, & ILogFile::onPrintError >( *pLogFile );
            compiler.sigWarning.connect_first< ILogFile, & ILogFile::onPrintWarning >( *pLogFile );

Now the ILogFile connection will be added at the beginning, and processed first.

Many-to-one and many-to-many connections
It's also perfectly valid to connect several signals to one slot. Here the msignals::managed_target base class becomes very handy, as it tracks all these connections, and automatically removes them when the target object is destroyed.

Generated on Tue Apr 22 21:55:23 2008 for msignals by  doxygen 1.5.5