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
Trang 1Usage
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
Trang 2connect 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"
Trang 3class 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
Trang 4Note 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
Trang 5I 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