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"; . 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. 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. 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