ROOM:
If you plan to use this in a large project or in a production project, my first suggestion is not to reinvent the wheel , but rather use Boost.Signals2 or alternative libraries. These libraries are not as complex as you think, and are likely to be more efficient than any special solution you could come up with.
This suggests that if your goal is more didactic and you want to play a little with these things to understand how they are implemented, I appreciate your spirit and try to answer your questions, but not before giving you tips for improvement.
CONSULTATIONS:
First of all, this sentence confuses:
"The connection and disconnection methods are still not thread safe. But I wanted to make sure that each instance of the slot (that is, the processor instance) cannot be used by another thread. I decided to use unique_ptr and std::move to implement the movement semantics for the slots."
Just in case, when you think about it (the “but” in your proposal assumes that), using unique_ptr does not save you from the need to protect your vector slots from data calculations. So you should still use the mutex to synchronize access to slots_vec anyway.
Second point: using unique_ptr you grant exclusive ownership of slot objects to a separate signal object. If I understand correctly, you declare that you are doing this in order to avoid using different streams associated with the same slot (which will force you to synchronize access to it).
I’m not sure that this is reasonable, reasonable. Firstly, it makes it impossible to register the same slot for several signals (I heard that you object that now you do not need it, but hold on). Secondly, you might want to change the state of these processors at runtime in order to adapt your response to the signals they receive. But if you do not have pointers to them, how would you do it?
Personally, at least I will go for shared_ptr , which will automatically control the lifetime of your slots; and if you don’t want multiple threads to mess up with these objects, just don’t give them access to them. Just avoid passing a generic pointer to these threads.
But I would go one step further : if your slots are callable objects, it seems to me, I would completely abandon shared_ptr and would rather use std::function<> to encapsulate them inside the Signal class. That is, I would just leave the vector objects std::function<> to call every time a signal is issued. That way, you would have more options than just inheriting from Slot to set up a callback: you could register a simple pointer to a function or the result of std::bind or just any functor you can think of (even a lambda).
Now you'll probably see that this is very similar to the design of Boost.Signals2. Please do not think that I am not ignoring the fact that your original design goal was to have something more subtle than that; I'm just trying to show you why a modern library is designed this way and why it makes sense to resort to it at the end.
Of course, registering std::function objects, rather than smart pointers in your Signal class, will make you care about the lifetime of those functors that you allocate on the heap; however, it is not necessarily responsible for the Signal class. You can create a wrapper class for this purpose that can contain common pointers to functors created on the heap (for example, class instances obtained from Slot ) and register them in the Signal object. With some adaptation, it will also allow you to register and disable slots individually, rather than "all or nothing."
ANSWERS:
But let's now assume that your requirements will always be (the last part is really hard to foresee), for example:
- You do not need to register the same slot for multiple signals;
- You do not need to change the state of the slot at runtime;
- You do not need to register different types of callbacks (lambdas, function pointers, functors, ...);
- You do not need to selectively disable individual slots.
Then here are the answers to your questions:
Q1: "[...] If I create an instance of Signal and an instance of Slot as follows, is it guaranteed that the arguments are passed as const refs?"
A1: Yes, they will be referred to as permalinks because everything in your forwarding path is a permalink.
Q2: "[In Boost.Signals2] you can actually connect any functor or function pointer. How does it work? It can be useful if boost :: function is used to connect any function pointer or method pointer to a signal"
A2: it is based on the template of the class boost::function<> (which later became std::function and should be supported as such in VS2010, if I remember correctly), which uses the type of erase methods to wrap called objects of different types, but identical signatures. If you are interested in learning the implementation details, see the implementation of boost::function<> or look at the MS implementation of std::function<> (it should be very similar).
Hope this helps you a bit. If not, feel free to ask additional questions in the comments.