Usage


When faced with needing more than one piece of code in a program to handle a given event, typical solutions involve callbacks through function pointers, or directly coded dependencies between the subsystem that fires the event and the subsystems that need to handle it. Circular dependencies are a common result of such designs. Using Boost.Signals, you gain flexibility and decoupling. To start using the library, include the header "boost/signals.hpp".[2]

The following example demonstrates the basic properties of signals and slots, including how to connect them and how to emit the signal. Note that a slot is something that you provide, either a function or a function object that is compatible with the function signature of the signal. In the following code, we create both a free function, my_first_slot, and a function object, my_second_slot; both are then connected to the signal that we create.

 #include <iostream> #include "boost/signals.hpp" void my_first_slot() {   std::cout << "void my_first_slot()\n"; } class my_second_slot { public:   void operator()() const {     std::cout <<       "void my_second_slot::operator()() const\n";   } }; int main() {   boost::signal<void ()> sig;   sig.connect(&my_first_slot);   sig.connect(my_second_slot());   std::cout << "Emitting a signal...\n";   sig(); } 

We start by declaring a signal, which expects slots that return void and take no arguments. We then connect two compatible slot types to that signal. For one, we call connect with the address of the free function, my_first_slot. For the other, we default-construct an instance of the function object my_second_slot and pass it to connect. These connections mean that when we emit a signal (by invoking sig), the two slots will be called immediately.

 sig(); 

When running the program, the output will look something like this:

 Emitting a signal... void my_first_slot() void my_second_slot::operator()() const 

However, the order of the last two lines is unspecified because slots belonging to the same group are invoked in an unspecified order. There is no way of telling which of our slots will be called first. Whenever the calling order of slots matters, you must put them into groups.

Grouping Slots

It is sometimes important to know that some slots are called before others, such as when the slots have side effects that other slots might depend upon. Groups is the name of the concept that supports such requirements. It is a signal template argument named Group that is, by default, int. The ordering of Groups is std::less<Group>, which translates to operator< for int. In other words, a slot belonging to group 0 is called before a slot in group 1, and so on. Note, however, that slots in the same group are called in an unspecified order. The only way to exactly control the order by which all slots are called is to put every slot into its own group.

A slot is assigned to a group by passing a Group to signal::connect. The group to which a connected slot belongs cannot be changed; to change the group for a slot, it must be disconnected and then reconnected to the signal in the new group.

As an example, consider two slots taking one argument of type int&; the first doubles the argument, and the second increases the current value by 3. Let's say that the correct semantics are that the value first be doubled, and then increased by 3. Without a specified order, we have no way of ensuring these semantics. Here's an approach that works on some systems, some of the time (typically when the moon is full and it's Monday or Wednesday).

 #include <iostream> #include "boost/signals.hpp" class double_slot {   public:   void operator()(int& i) const {     i*=2;   } }; class plus_slot { public:   void operator()(int& i) const {     i+=3;   } }; int main() {   boost::signal<void (int&)> sig;   sig.connect(double_slot());   sig.connect(plus_slot());   int result=12;   sig(result);   std::cout << "The result is: " << result << '\n'; } 

When running this program, it might produce this output:

 The result is: 30 

Or, it might produce this:

 The result is: 27 

There's simply no way of guaranteeing the correct behavior without using groups. We need to ensure that double_slot is always called before plus_slot. That requires that we specify that double_slot belongs to a group that is ordered before plus_slot's group, like so:

 sig.connect(0,double_slot()); sig.connect(1,plus_slot()); 

This ensures that we'll get what we want (in this case, 27). Again, note that for slots belonging to the same group, the order with which they are called is unspecified. As soon as a specific ordering of slot invocation is required, make sure to express that using different groups.

Note that the type of Groups is a template parameter to the class signal, which makes it possible to use, for example, std::string as the type.

 #include <iostream> #include <string> #include "boost/signals.hpp" class some_slot {   std::string s_; public:   some_slot(const std::string& s) : s_(s) {}   void operator()() const {     std::cout << s_ << '\n';   } }; int main() {   boost::signal<void (),     boost::last_value<void>,std::string> sig;   some_slot s1("I must be called first, you see!");   some_slot s2("I don't care when you call me, not at all. \     It'll be after those belonging to groups, anyway.");   some_slot s3("I'd like to be called second, please.");   sig.connect(s2);   sig.connect("Last group",s3);   sig.connect("First group",s1);   sig(); } 

First we define a slot type that prints a std::string to std::cout when it is invoked. Then, we get to the declaration of the signal. Because the Groups parameter comes after the Combiner type, we must specify that also (we'll just declare the default). We then set the Groups type to std::string.

 boost::signal<void (),boost::last_value<void>,std::string> sig; 

We accept the defaults for the rest of the template parameters. When connecting the slots s1, s2, and s3, the groups that we create are lexicographically ordered (because that's what std::less<std::string> does), so "First group" precedes "Last group". Note that because these string literals are implicitly convertible to std::string, we are allowed to pass them directly to the connect function of signal. Running the program tells us that we got it right.

 I must be called first, you see! I'd like to be called second, please. I don't care when you call me, not at all. It'll be after those belonging to groups, anyway. 

We could have opted for another ordering when declaring the signal typefor example, using std::greater.

 boost::signal<void (),boost::last_value<void>,   std::string,std::greater<std::string> > sig; 

Had we used that in the example, the output would be

 I'd like to be called second, please. I must be called first, you see! I don't care when you call me. 

Of course, for this example, std::greater produces an ordering that leads to the wrong output, but that's another story. Groups are useful, even indispensable, but it's not always trivial to assign the correct group values, because connecting slots isn't necessarily performed from the same location in the code. It can thus be a problem to know which value should be used for a particular slot. Sometimes, this problem can be solved with disciplinethat is, commenting the code and making sure that everyone reads the commentsbut this only works when there aren't many places in the code where the values are assigned and when there are no lazy programmers. In other words, this approach doesn't work. Instead, you need a central source of group values that can be generated based upon some supplied value that is unique to each slot or, if the dependent slots know about each other, the slots could provide their own group value.

Now that you know how to deal with issues of slot call ordering, let's take a look at different signatures for your signals. You often need to pass additional information along about important events in your systems.

Signals with Arguments

Often, there is additional data to be passed to a signal. For example, consider a temperature guard that reports drastic changes in the temperature. Just knowing that the guard detected a problem can be insufficient; a slot may need to know the current temperature. Although both the guard (the signal) and the slot could access the temperature from a common sensor, it may be simplest to have the guard pass the current temperature to the slot when invoking it. As another example, consider when slots are connected to several signals: The slots will most likely need to know which signal invoked it. There are myriad use cases that require passing some information from signal to slot. The arguments that slots expect are part of a signal's declaration. The first argument to the signal class template is the signature for invoking the signal, and this signature is also used for the connected slots when the signal calls them. If we want the argument to be modifiable, we make sure that it is passed by non-const reference or pass a pointer, else we can pass it by value or reference to const. Note that besides the obvious difference that the original argument is either modifiable or not, this also has implications for the types of acceptable arguments to the signal itself and to the slot typeswhen the signal expects an argument by value or as reference to const, types that are implicitly convertible to the argument's type can be used to emit a signal. Likewise for slotsif the slot accepts its arguments by value or reference to const, this means that implicit conversion to that type from the actual argument type of the signal is allowed. We'll see more of this soon, as we consider how to properly pass arguments when signaling.

Consider an automatic parking lot guard, which receives notifications as soon as a car enters or leaves the parking lot. It needs to know something unique about the carfor example, the car's registration number, so it can track the coming and going of each. (That would also permit the system to know just how ridiculous a sum to charge the owner according to the elapsed time.) The guard should also have a signal of its own, to be able to trigger an alarm when someone is trying to cheat. There needs to be a few security guards to listen to that signal, which we'll model using a class called security_guard. Finally, let's also add a gate class, which contains a signal that is signaled whenever a car enters or leaves the parking lot. (The parking_lot_guard will definitely be interested in knowing about this.) Let's look first at the declaration for the parking_lot_guard.

 class parking_lot_guard {   typedef     boost::signal<void (const std::string&)> alarm_type;   typedef alarm_type::slot_type slot_type;   boost::shared_ptr<alarm_type> alarm_;   typedef std::vector<std::string> cars;   typedef cars::iterator iterator;   boost::shared_ptr<cars> cars_; public:   parking_lot_guard();   boost::signals::connection     connect_to_alarm(const slot_type& a);   void operator()(bool is_entering,const std::string& car_id); private:   void enter(const std::string& car_id);   void leave(const std::string& car_id); }; 

There are three especially important parts to look at closely here; first of these is the alarm, which is a boost::signal that returns void and takes a std::string (which will be the identifier of the car). The declaration of such a signal is worth looking at again.

 boost::signal<void (const std::string&)> 

It's just like a function declaration, only without a function name. When in doubt, try to remember that there's really nothing more to it than that! It is possible to connect to this signal from outside using the member function connect_to_alarm. (We'll address how and why we would want to sound this alarm when implementing this class.) The next thing to note is that both the alarm and the container of car identifiers (a std::vector containing std::strings) are stored in boost::shared_ptrs. The reason for this is that although we only intend to declare one instance of parking_lot_guard, there are going to be multiple copies of it; because this guard class will also be connected to other signals later on, which will create copies (Boost.Signals copies the slots, which is required for managing lifetime correctly); but we still want all of the data to be available, so we share it. We could have avoided copying altogetherfor example, by using pointers or externalizing the slot behavior from this classbut doing it this way nicely illuminates a trap that's easy to fall into. Finally, note that we have declared a function call operator, and the reason for this is that we are going to connect the parking_lot_guard to a signal in the gate class (that we have yet to declare).

Let's turn our attention to the security_guard class.

 class security_guard {   std::string name_; public:   security_guard (const char* name);   void do_whatever_it_takes_to_stop_that_car() const;   void nah_dont_bother() const;   void operator()(const std::string& car_id) const; }; 

The security_guards don't really do much. The class has a function call operator, which is used as a slot for alarms from the parking_lot_guard, and just two other functions: One is for trying to stop cars for which the alarm goes off, and the other is used to do nothing. This brings us to the gate class, which detects when cars arrive at the parking lot, and when they leave it.

 class gate {   typedef     boost::signal<void (bool,const std::string&)> signal_type;   typedef signal_type::slot_type slot_type;   signal_type enter_or_leave_; public:   boost::signals::connection     connect_to_gate(const slot_type& s);   void enter(const std::string& car_id);   void leave(const std::string& car_id); }; 

You'll note that the gate class contains a signal that is to be emitted when a car either enters or leaves the parking lot. There is a public member function (connect_to_gate) for connecting to this signal, and two more functions (enter and leave) that are to be called as cars come and go.

Now that we know the players, it's time to take a stab at implementing them. Let's start with the gate class.

 class gate {   typedef     boost::signal<void (bool,const std::string&)> signal_type;   typedef signal_type::slot_type slot_type;   signal_type enter_or_leave_; public:   boost::signals::connection     connect_to_gate(const slot_type& s) {     return enter_or_leave_.connect(s);   }   void enter(const std::string& car_id) {     enter_or_leave_(true,car_id);   }   void leave(const std::string& car_id) {     enter_or_leave_(false,car_id);   } }; 

The implementation is trivial. Most of the work is forwarded to other objects. The function connect_to_gate simply forwards a call to connect for the signal enter_or_leave_. The function enter signals the signal, passing TRue (this means that the car is entering) and the identifier of the car. leave does the same thing, but passes false, indicating that the car is leaving. A simple class for a simple chore. The security_guard class isn't much more complicated.

 class security_guard {   std::string name_; public:   security_guard (const char* name) : name_(name) {}   void do_whatever_it_takes_to_stop_that_car() const {     std::cout <<       "Stop in the name of...eh..." << name_ << '\n'; }   void nah_dont_bother() const {     std::cout << name_ <<       " says: Man, that coffee tastes f i n e fine!\n";   }   void operator()(const std::string& car_id) const {     if (car_id.size() && car_id[0]=='N')       do_whatever_it_takes_to_stop_that_car();     else       nah_dont_bother();   } }; 

security_guards know their names, and they can decide whether to do something when the alarm goes off (if the car_id starts with the letter N, they spring into action). The function call operator is the slot function that is calledsecurity_guard objects are function objects, and adhere to the requirements for parking_lot_guard's alarm_type signal. Things get a little more complicated with parking_lot_guard, but not too much.

 class parking_lot_guard {   typedef     boost::signal<void (const std::string&)> alarm_type;   typedef alarm_type::slot_type slot_type;   boost::shared_ptr<alarm_type> alarm_;   typedef std::vector<std::string> cars;   typedef cars::iterator iterator;   boost::shared_ptr<cars> cars_; public:   parking_lot_guard()     : alarm_(new alarm_type), cars_(new cars) {}   boost::signals::connection     connect_to_alarm(const slot_type& a) {     return alarm_->connect(a);   }   void operator()     (bool is_entering,const std::string& car_id) {     if (is_entering)       enter(car_id);     else       leave(car_id);   } private:   void enter(const std::string& car_id) {     std::cout <<       "parking_lot_guard::enter(" << car_id << ")\n";     // If the car already exists here, sound the alarm     if (std::binary_search(cars_->begin(),cars_->end(),car_id))       (*alarm_)(car_id);     else // Insert the car_id       cars_->insert(         std::lower_bound(           cars_->begin(),           cars_->end(),car_id),car_id);   }   void leave(const std::string& car_id) {     std::cout <<       "parking_lot_guard::leave(" << car_id << ")\n";     // If there are no registered cars,     // or if the car isn't registered, sound the alarm     std::pair<iterator,iterator> p=       std::equal_range(cars_->begin(),cars_->end(),car_id);     if (p.first==cars_->end() || *(p.first)!=car_id)        (*alarm_)(car_id);     else       cars_->erase(p.first);   } }; 

That's it! (Of course, as we haven't connected any slots to any signals yet, there's a bit more to do. Still, these classes are remarkably simple for what they're about to do.) To make the shared_ptrs for the alarm and the car identifiers behave correctly, we implement the default constructor, where the signal and the vector are properly allocated. The implicitly created copy constructor, the destructor, and the assignment operator will all do the right thing (thanks to these fine smart pointers). The function connect_to_alarm forwards to call to the contained signal's connect. The function call operator tests the Boolean argument to see whether the car is entering or leaving, and makes a call to the corresponding function enter/leave. In the function enter, the first thing that's done is to search through the vector for the identifier of the car. Finding it would mean that something has gone wrong; perhaps someone has stolen a license plate. The search is performed using the algorithm binary_search,[3] which expects a sorted sequence (we make sure that it always is sorted). If we do find the identifier, we immediately sound the alarm, which involves invoking the signal.

 (*alarm_)(car_id); 

We need to dereference alarm_ first, because alarm_ is a boost::shared_ptr, and when invoking it, we pass to it the argument that is the car identifier. If we don't find the identifier, all is well, and we insert the car identifier into cars_ at the correct place. Remember that we need the sequence to be sorted at all times, and the best way to ensure that is by inserting elements in a location such that the ordering isn't compromised. Using the algorithm lower_bound gives us this location in the sequence (this algorithm also expects a sorted sequence). Last but not least is the function leave, which is called when cars are leaving through the gates of our parking lot. leave starts with making sure that the car has been registered in our container for car identifiers. This is done using a call to the algorithm equal_range, which returns a pair of iterators that denote the range where an element could be inserted without violating the ordering. This means that we must dereference the returned iterator and make sure that its value is equal to the one we're looking for. If we didn't find it, we trigger the alarm again, and if we did find it, we simply remove it from the vector. You'll note that we didn't call any code for charging the people who park here; such evil logic is well beyond the scope of this book.

With all of the participants in our parking lot defined, we must connect the signals and slots or nothing will happen! The gate class knows nothing about the parking_lot_guard class, which in turn knows nothing about the security_guard class. This is a feature of this library: Types signaling events need not have any knowledge of the types receiving the events. Getting back to the example, let's see if we can get this parking lot up and running.

 int main() {  // Create some guards   std::vector<security_guard> security_guards;   security_guards.push_back("Bill");   security_guards.push_back("Bob");   security_guards.push_back("Bull");   // Create two gates   gate gate1;   gate gate2;   // Create the automatic guard   parking_lot_guard plg;   // Connect the automatic guard to the gates   gate1.connect_to_gate(plg);   gate2.connect_to_gate(plg);   // Connect the human guards to the automatic guard   for (unsigned int i=0;i<security_guards.size();++i) {     plg.connect_to_alarm(security_guards[i]);   }   std::cout << "A couple of cars enter...\n";   gate1.enter("SLN 123");   gate2.enter("RFD 444");   gate2.enter("IUY 897");   std::cout << "\nA couple of cars leave...\n";   gate1.leave("IUY 897");   gate1.leave("SLN 123");   std::cout << "\nSomeone is entering twice - \     or is it a stolen license plate?\n";   gate1.enter("RFD 444"); } 

There you have ita fully functional parking lot. We did it by creating three security_guards, two gates, and a parking_lot_guard. These know nothing about each other, but we still needed to hook them up with the correct infrastructure for communicating important events that take place in the lot. That means connecting the parking_lot_guard to the two gates.

 gate1.connect_to_gate(plg); gate2.connect_to_gate(plg); 

This makes sure that whenever the signal enter_or_leave_ in the instances of gate is signaled, parking_lot_guard is informed of this event. Next, we have the security_guards connect to the signal for the alarm in parking_lot_guard.

 plg.connect_to_alarm(security_guards[i]); 

We have managed to decouple these types from each other, yet they have exactly the right amount of information to perform their duties. In the preceding code, we put the parking lot to the test by letting a few cars enter and leave. This real-world simulation reveals that we have managed to get all of the pieces to talk to each other as required.

 A couple of cars enter... parking_lot_guard::enter(SLN 123) parking_lot_guard::enter(RFD 444) parking_lot_guard::enter(IUY 897) A couple of cars leave... parking_lot_guard::leave(IUY 897) parking_lot_guard::leave(SLN 123) Someone is entering twice - or is it a stolen license plate? parking_lot_guard::enter(RFD 444) Bill says: Man, that coffee tastes f.i.n.e fine! Bob says: Man, that coffee tastes f.i.n.e fine! Bull says: Man, that coffee tastes f.i.n.e fine! 

It's a sad fact that the fraudulent people with license plate RFD 444 got away, but you can only do so much.

This has been a rather lengthy discussion about arguments to signalsin fact, we have covered much more than that when examining the very essence of Signals' usefulness, the decoupling of types emitting signals and types with slots listening to them. Remember that any types of arguments can be passed, and the signature is determined by the declaration of the signal typethis declaration looks just like a function declaration without the actual function name. We haven't talked at all about the return type, although that is certainly part of the signature, too. The reason for this omission is that the return types can be treated in different ways, and next we'll look at why and how.

Combining the Results

When the signature of a signal and its slots have non-void return type, it is apparent that something happens to the return values of the slots, and indeed, that invoking the signal yields a result of some kind. But what is that result? The signal class template has a parameterizing type named Combiner, which is a type responsible for combining and returning values. The default Combiner is boost::last_value, which is a class that simply returns the value for the last slot it calls. Now, which slot is that? We typically don't know, because the ordering of slot calls is undefined within the groups.[4] Let's start with a small example that demonstrates the default Combiner.

[4] So, assuming that the last group has only one slot, we do know.

 #include <iostream> #include "boost/signals.hpp"   bool always_return_true() {     return true;   }   bool always_return_false() {     return false;   } int main() {   boost::signal<bool ()> sig;   sig.connect(&always_return_true);   sig.connect(&always_return_false);   std::cout << std::boolalpha << "True or false? " << sig(); } 

Two slots, always_return_true and always_return_false, are connected to the signal sig, which returns a bool and takes no argument. The result of invoking sig is printed to cout. Will it be TRue or false? We cannot know for sure without testing (when I tried it, the result was false). In practice, you either don't care about the return value from invoking the signal or you need to create your own Combiner in order to get more meaningful, customized behavior. For example, it may be that the results from all of the slots accrue into the net result desired from invoking the signal. In another case, it may be appropriate to avoid calling any more slots if one of the slots returns false. A custom Combiner can make those things, and more, possible. This is because the Combiner iterates through the slots, calls them, and decides what to do with the return values from them.

Consider an initialization sequence, in which any failure should terminate the entire sequence. The slots could be assigned to groups according to the order with which they should be invoked. Without a custom Combiner, here's how it would look:

 #include <iostream> #include "boost/signals.hpp" bool step0() {   std::cout << "step0 is ok\n";   return true; } bool step1() {   std::cout << "step1 is not ok. This won't do at all!\n";   return false; } bool step2() {   std::cout << "step2 is ok\n";   return true; } int main() {   boost::signal<bool ()> sig;   sig.connect(0,&step0);   sig.connect(1,&step1);   sig.connect(2,&step2);   bool ok=sig();   if (ok)     std::cout << "All system tests clear\n";   else     std::cout << "At least one test failed. Aborting.\n"; } 

With the preceding code, there is no way that the code will ever know that one of the tests has failed. As you recall, the default combiner is boost::last_value, and it simply returns the value of the last slot call, which is the call to step2. Running the example as-is gives us this disappointing output:

 step0 is ok step1 is not ok. This won't do at all! step2 is ok All system tests clear 

This is clearly not the right result. We need a Combiner that stops processing when a slot returns false, and propagates that value back to the signal. A Combiner is nothing more than a function object with a couple of additional requirements. It must have a typedef called result_type, which is the return type of its function call operator. Furthermore, the function call operator must be parameterized on the iterator type with which it will be invoked. The Combiner that we need right now is quite simple, so it serves as a good example.

 class stop_on_failure { public:   typedef bool result_type;   template <typename InputIterator>   bool operator()(InputIterator begin,InputIterator end) const   {     while (begin!=end) {       if (!*begin)         return false;       ++begin;     }     return true;   } }; 

Note the public typedef result_type, which is bool. The type of result_type doesn't necessarily relate to the return type of the slots. (When declaring the signal, you specify the signature of the slots and the arguments of the signal's function call operator. However, the return type of the Combiner determines the return type of the signal's function call operator. By default, this is the same as the return type of the slots, but it doesn't have to be the same.) stop_on_failure's function call operator, which is parameterized on a slot iterator type, iterates over the range of slots and calls each one; unless we encounter an error. For stop_on_failure, we don't want to continue calling the slots on an error return, so we test the return value for each call. If it is false, the function returns immediately, else it continues invoking the slots. To use stop_on_failure, we simply say so when declaring the signal type:

 boost::signal<bool (),stop_on_failure> sig; 

If we had used this in the bootstrap sequence from the previous example, the output would have been exactly as we had intended.

 step0 is ok step1 is not ok. This won't do at all! At least one test failed. Aborting. 

Another useful type of Combiner is one that returns the maximum or the minimum, of all of the values returned by the invoked slots. Many other interesting Combiners are possible, including one to save all results in a container. The (excellent) online documentation for this library has an example of just such a Combinerbe sure to read it! It's not every day you'll need to write your own Combiner class, but now and then doing so creates an especially elegant solution to an otherwise complicated problem.

Signals Can Never Be Copied

I have already mentioned that signals cannot be copied, but it's worth mentioning what one should do about classes that contain a signal. Must these classes be non-copyable too? No they don't have to be, but the copy constructor and the assignment operator needs to be implemented by hand. Because the signal class declares the copy constructor and the assignment operator private, a class aggregating signals has to implement the semantics that apply for it. One way to handle copies correctly is to use shared signals between instances of a class, which is what we did in the parking lot example. There, every instance of parking_lot_guard referred to the same signal by means of boost::shared_ptr. For other classes, it makes sense to simply default-construct the signal in the copy, because the copy semantics don't include the connection of the slots. There are also scenarios where copying of classes containing a signal doesn't make sense, in which one can rely on the non-copyable semantics of the contained signal to ensure that copying and assignment is verboten. To give a better view of what's going on here, consider the class some_class, defined here:

 class some_class {   boost::signal<void (int)> some_signal; }; 

Given this class, the copy constructor and the assignment operator that the compiler generates isn't usable. If code is written that tries to use these, the compiler complains. For example, the following example tries to copy construct the some_class sc2 from sc1:

 int main() {   some_class sc1;   some_class sc2(sc1); } 

When compiling this program, the compiler-generated copy constructor tries to perform a member-by-member copy of the members of some_class. Because of the private copy constructor of signal, the compiler says something like this:

 c:/boost_cvs/boost/boost/noncopyable.hpp: In copy constructor `   boost::signals::detail::signal_base::signal_base(const   boost::signals::detail::signal_base&)': c:/boost_cvs/boost/boost/noncopyable.hpp:27: error: `   boost::noncopyable::noncopyable(     const boost::noncopyable&)' is private noncopyable_example.cpp:10: error: within this context 

So whatever your copying and assignment of classes containing signals need to do, make sure that need doesn't include copying of the signals!

Managing Connections

We have covered how to connect slots to signals, but we haven't yet seen how to disconnect them. There are many reasons to not leave a slot connected permanently to a signal. Until now, we've ignored it, but boost::signal::connect returns an instance of boost::signals::connection. Using this connection object, it is possible to disconnect a slot from the signal, as well as test whether the slot is currently connected to the signal. connections are a sort of handle to the actual link between the signal and the slot. Because the knowledge of the connection between a signal and a slot is tracked separately from both, a slot doesn't know if or when it is connected. If a slot won't be disconnected from a signal, it's typically fine to just ignore the connection returned by signal::connect. Also, a call to disconnect for the group to which a slot belongs, or a call to disconnect_all_slots will disconnect a slot without the need for the slot's connection. If it's important to be able to test whether a slot is still connected to a signal, there is no other way than by saving the connection and using it to query the signal.

The connection class provides operator<, which makes it possible to store connections in the Standard Library containers. It also provides operator== for completeness. Finally, the class provides a swap member function that swaps the signal/slot connection knowledge of one connection with that of another. The following example demonstrates how to use the signals::connection class:

 #include <iostream> #include <string> #include "boost/signals.hpp" class some_slot_type {   std::string s_; public:   some_slot_type(const char* s) : s_(s) {}   void operator()(const std::string& s) const {     std::cout << s_ << ": " << s << '\n';   } }; int main() {   boost::signal<void (const std::string&)> sig;   some_slot_type sc1("sc1");   some_slot_type sc2("sc2");   boost::signals::connection c1=sig.connect(sc1);   boost::signals::connection c2=sig.connect(sc2);  // Comparison   std::cout << "c1==c2: " << (c1==c2) << '\n';   std::cout << "c1<c2:  " << (c1<c2) << '\n';  // Testing the connection   if (c1.connected())     std::cout << "c1 is connected to a signal\n";  // Swapping and disconnecting   sig("Hello there");   c1.swap(c2);   sig("We've swapped the connections");   c1.disconnect();   sig("Disconnected c1, which referred to sc2 after the swap"); } 

There are two connection objects in the example, and we saw that they can be compared using operator< and operator==. The ordering relation that operator< implements is unspecified; it exists to support storing connections in the Standard Library containers. Equality through operator== is well defined, however. If two connections reference the exact same physical connection, they are equal. If two connections do not reference any connection, they are equal. No other pairs of connections are equal. In the example, we also disconnected a connection.

 c1.disconnect(); 

Although c1 originally refers to the connection for sc1, at the time of disconnecting it refers to sc2, because we swap the contents of the two connections using the member function swap. Disconnecting means that the slot no longer is notified when the signal is signaled. Here is the output from running the program:

 c1==c2: 0 c1<c2:  1 c1 is connected to a signal sc1: Hello there sc2: Hello there sc1: We've swapped the connections sc2: We've swapped the connections sc1: Disconnected c1, which referred to sc2 after the swap 

As you can see, the last invocation of the signal sig only invokes the slot sc1.

Sometimes, the lifetime of the connection for a slot relates to a certain scope in the code. This is similar to any other resource that is required for a given scope, something that is handled using smart pointers or other mechanisms for scoping. Boost.Signals provides a scoped version for connections called scoped_connection. A scoped_connection makes sure that the connection is disconnected as the scoped_connection is destroyed. The constructor of scoped_connection takes a connection object as argument, which it assumes ownership of in a way.

 #include <iostream> #include "boost/signals.hpp" class slot { public:   void operator()() const {     std::cout << "Something important just happened!\n";   } }; int main() {   boost::signal<void ()> sig;   {     boost::signals::scoped_connection s=sig.connect(slot());   }   sig(); } 

The boost::signals::scoped_connection s is given a small scope inside of main, and after leaving that scope the signal sig is invoked. There is no output from that, because the scoped_connection has already terminated the connection between the slot and the signal. Using scoped resources like this simplifies the code and maintenance thereof.

Creating Slots Using Bind and Lambda

You've seen how useful and flexible Signals is. However, you'll find even more power when you combine Boost.Signals with Boost.Bind and Boost.Lambda. Those two libraries, covered in detail in "Library 9: Bind 9" and "Library 10: Lambda 10," help to create function objects on-the-fly. That means it is possible to create slots (and slot types) right at the point where they are connected to a signal, rather than having to write a special, single-purpose class for a slot, create an instance, and then connect it. It also puts the slot logic right where it's used rather than in a separate part of the source code. Finally, these libraries even make it possible to adapt existing classes that don't provide a function call operator but have other, suitable means for handling signals.

In the first example to follow, we'll have a look at how neatly lambda expressions can be used to create a few slot types. The slots will be created right in the call to connect. The first one simply prints a message to std::cout when the slot is invoked. The second checks the value of the string passed through the signal to the slot. If it equals "Signal", it prints one message; otherwise, it prints another message. (These examples are rather contrived, but the expressions could perform any kind of useful computation.) The last two slots created in the example will do exactly what double_slot and plus_slot did in an example earlier in the chapter. You'll find the lambda versions far more readable.

 #include <iostream> #include <string> #include <cassert> #include "boost/signals.hpp" #include "boost/lambda/lambda.hpp" #include "boost/lambda/if.hpp" int main() {   using namespace boost::lambda;   boost::signal<void (std::string)> sig;   sig.connect(var(std::cout)     << "Something happened: " << _1 << '\n');   sig.connect(     if_(_1=="Signal") [       var(std::cout) << "Ok, I've got it\n"]     .else_[       std::cout << constant("Yeah, whatever\n")]);   sig("Signal");   sig("Another signal");   boost::signal<void (int&)> sig2;   sig2.connect(0,_1*=2); // Double it   sig2.connect(1,_1+=3); // Add 3   int i=12;   sig2(i);   assert(i==27); } 

If you aren't yet familiar with lambda expressions in C++ (or otherwise), don't worry if the preceding seems a bit confusingyou may want to read the chapters on Bind and Lambda first and return to these examples. If you already have, I am sure that you appreciate the terse code that results from using lambda expressions; and it avoids cluttering the code with small function objects, too.

Now let's take a look at using binders to create slot types. Slots must implement a function call operator, but not all classes that would otherwise be suitable as slots do so. In those cases, it's often possible to use existing member functions of classes, repackaged for use as slots using binders. Binders can also help readability by allowing the function (rather than function object) that handles an event to have a meaningful name. Finally, there are situations in which the same object must respond to different events, each with the same slot signature, but different reactions. Thus, such objects need different member functions to be called for different events. In each of these cases, there is no function call operator suitable for connecting to a signal. Thus, a configurable function object is needed, and Boost.Bind provides (as the bind facility in Boost.Lambda) the means to do that.

Consider a signal that expects a slot type that returns bool and accepts an argument of type double. Assuming a class some_class with a member function some_function that has the correct signature, how do you connect some_class::some_function to the signal? One way would be to add a function call operator to some_class, and have the function call operator forward the call to some_function. That means changing the class interface unnecessarily and it doesn't scale well. A binder works much better.

 #include <iostream> #include "boost/signals.hpp" #include "boost/bind.hpp" class some_class { public:   bool some_function(double d) {     return d>3.14;   }   bool another_function(double d) {     return d<0.0;   } }; int main() {   boost::signal<bool (double)> sig0;   boost::signal<bool (double)> sig1;   some_class sc;   sig0.connect(     boost::bind(&some_class::some_function,&sc,_1));   sig1.connect(     boost::bind(&some_class::another_function,&sc,_1));   sig0(3.1);   sig1(-12.78); } 

Binding this way has an interesting side effect: It avoids unnecessary copying of some_class instances. The binder holds a pointer to the some_class instance and it's the binder that the signal copies. Unfortunately, there's a potential lifetime management issue with this approach: If sc is destroyed and then one of the signals is invoked, undefined behavior results. That's because the binder will have a dangling pointer to sc. By avoiding the copies, we must also assume the responsibility of keeping the slots alive so long as a connection exists that (indirectly) references them. Of course, that's what reference-counting smart pointers are for, so the problem is easy to solve.

Using binders like this is common when using Boost.Signals. Whether you use lambda expressions to create your slots or binders to adapt existing classes for use as slot types, you'll soon value the synergy among Boost.Signals, Boost.Lambda, and Boost.Bind. It will save you time and make your code elegant and succinct.



    Beyond the C++ Standard Library(c) An Introduction to Boost
    Beyond the C++ Standard Library: An Introduction to Boost
    ISBN: 0321133544
    EAN: 2147483647
    Year: 2006
    Pages: 125

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net