msignals is a specialised library providing a "signals & slots" communication mechanism for C++ objects. There already exist several libraries providing similar functionality. The most notable ones are:
Each of them has its own strengths and weaknesses, there are articles on the web that compare these popular implementations, so we won't do it here. However, you may ask the question -- why another one? The reasons for writing msignals and its design assumptions are listed below.
- Small abstraction penalty
- Abstraction penalty is plaguing many otherwise excellent, modern C++ projects. It can seriously hurt run-time performance in cases when the compiler is unable to optimize the code in satisfactory degree. But also it affects another code quality aspect: debuggability. Presence of multiple layers of heavily-templatized code, serving only mundane, infrastructural tasks (like calling slots) obfuscates information visible to the user and makes the whole picture harder to see. It's doesn't make debugging impossible, except in cases when the debugger crashes in presence of complicated C++ symbols (quite common situation in some pogramming environments). However, it's annoying. Also we could ask ourselves, do we really need five layers on the stack to simply call another function from some list? Definitely not, and msignals is a proof of it. Sending a signal creates only two stack frames. Actually the slot call can be inlined (if the slot is a function allowed to inline), making it only one extra frame.
- Small abstraction penalty also means improved runtime performance, especially when using a compiler lacking a good optimizer. It's not always a choice of the developer what compiler to use, or what switches to pass during the build. In large companies there are standard environment configurations enforced by the management. These build environment specs usually don't favor pure performance settings, as it exposes compiler bugs and makes builds go longer. If you work in such company, msignals may help you.
- Small dependency penalty
- msignals is header-only library. It doesn't require to build/link to anything. Some other implementations require either to link to precompiled C++ code (like boost) or to use special preprocessor (like Qt), along with linking. msignals is purely self-contained, it does not depend on anything, even the standard library. You can simply drop it in, use it, and don't have to bother all these angry build guys, managers and lawyers which must be involved to approve a new dependency or build configuration change.
- Small code size overhead
- msignals produces small amount of symbols in the object code, imposing a small overhead on the debug info size and amount of work for the linker.
- Simple design and pragmatic implementation
- msignals design is very simple, although the code may seem as long, ugly-looking and badly organized. This is a deliberate choice. Many functions contain nearly-identical snippets of code, copied around everywhere. If they were refactored into function calls, there would make both performance overhead and debug info/symbols overhead. Inlining would not solve the second problem. Usage of the preprocessor would lead to undebuggable and even more ugly code. Most of these snippets are used to maintain custom single-linked list. Usage of STL list here would lead to enormous debug info/symbols overhead, and reduction of debuggability. msignals solves many such trade-offs with favor to general simplicity, and other priorities listed above.
- Principle of doing exactly one thing well
- msignals is a small library, performing one particular task. It's meant to be one of basic building blocks for more complex mechanisms.
What were definitely NOT goals of
msignals design:
- To add a great number of exotic, rarely used features.
- To write a beautifully looking code.
- To write a good example for some academic programming/design paradigm.
- Signals with zero to sixteen arguments with no additional type restrictions.
- Two forms of slots: delegates (object+method) or arbitrary functors.
- Delegates are much lighter and faster than bound functors, however they must have the same number of arguments as the signal, with convertible types.
- Const methods can be delegates.
- Both manual and automatic connection management.
- Registration of slots as both first-to-call and last-to-call.
- Support for conditional slots returning boolean value, which may be used to suppress calling the remaining slots.
- Objects representing signals can be passed around by value, as they have shared reference semantics (with reference counter). They are also compatible with standard containers.
Some features that other implementations have, were not included in msignals:
- Connecting signals to slots with different number of arguments. Some of the other signals implementations offer automatic argument discarding, providing default values to excess arguments of the slots, or binding. This is not implemented in msignals. However, you can emulate this using functor slots, combined with some binding mechanism (e.g. boost::lambda or boost::bind).
- Delegates to copyable objects. If you use delegates, your target objects should not change their addresses, because a reference to the target is stored in the delegate. This implies noncopyability. There are base classes msignals::target and msignals::managed_target which enforce noncopyability. It's recommended that you derive from one of these classes, although not required.
- Run-time introspection. This requires a complicated framework to implement, which is beyond the scope of msignals.