This tutorial is not meant to be read linearly. Its top-level structure roughly separates different concepts in the library (e.g., handling calling multiple slots, passing values to and from slots) and in each of these concepts the basic ideas are presented first and then more complex uses of the library are described later. Each of the sections is marked Beginner, Intermediate, or Advanced to help guide the reader. The Beginner sections include information that all library users should know; one can make good use of the Signals library after having read only the Beginner sections. The Intermediate sections build on the Beginner sections with slightly more complex uses of the library. Finally, the Advanced sections detail very advanced uses of the Signals library, that often require a solid working knowledge of the Beginner and Intermediate topics; most users will not need to read the Advanced sections. Boost.Signals has two syntactical forms: the preferred form and the compatibility form. The preferred form fits more closely with the C++ language and reduces the number of separate template parameters that need to be considered, often improving readability; however, the preferred form is not supported on all platforms due to compiler bugs. The compatible form will work on all compilers supported by Boost.Signals. Consult the table below to determine which syntactic form to use for your compiler. Users of Boost.Function, please note that the preferred syntactic form in Signals is equivalent to that of Function's preferred syntactic form. If your compiler does not appear in this list, please try the preferred syntax and report your results to the Boost list so that we can keep this table up-to-date.
The following example writes "Hello, World!" using signals and
slots. First, we create a signal
Calling a single slot from a signal isn't very interesting, so we can make the Hello, World program more interesting by splitting the work of printing "Hello, World!" into two completely separate slots. The first slot will print "Hello" and may look like this: struct Hello { void operator()() const { std::cout << "Hello"; } }; The second slot will print ", World!" and a newline, to complete the program. The second slot may look like this: struct World { void operator()() const { std::cout << ", World!" << std::endl; } }; Like in our previous example, we can create a signal
By default, slots are called in first-in first-out (FIFO) order, so the output of this program will be as expected: Hello, World! Slots are free to have side effects, and that can mean that some
slots will have to be called before others even if they are not connected in that order. The Boost.Signals
library allows slots to be placed into groups that are ordered in
some way. For our Hello, World program, we want "Hello" to be
printed before ", World!", so we put "Hello" into a group that must
be executed before the group that ", World!" is in. To do this, we
can supply an extra parameter at the beginning of the
This program will correctly print "Hello, World!", because the
struct GoodMorning
{
void operator()() const
{
std::cout << "... and good morning!" << std::endl;
}
};
sig.
... we will get the result we wanted: Hello, World! ... and good morning! Signals can propagate arguments to each of the slots they call. For instance, a signal that propagates mouse motion events might want to pass along the new mouse coordinates and whether the mouse buttons are pressed. As an example, we'll create a signal that passes two
void print_sum(float x, float y) { std::cout << "The sum is " << x+y << std::endl; } void print_product(float x, float y) { std::cout << "The product is " << x*y << std::endl; } void print_difference(float x, float y) { std::cout << "The difference is " << x-y << std::endl; } void print_quotient(float x, float y) { std::cout << "The quotient is " << x/y << std::endl; }
This program will print out the following: The sum is 8 The product is 15 The difference is 2 The quotient is 1.66667 So any values that are given to Just as slots can receive arguments, they can also return values. These values can then be returned back to the caller of the signal through a combiner. The combiner is a mechanism that can take the results of calling slots (there many be no results or a hundred; we don't know until the program runs) and coalesces them into a single result to be returned to the caller. The single result is often a simple function of the results of the slot calls: the result of the last slot call, the maximum value returned by any slot, or a container of all of the results are some possibilities. We can modify our previous arithmetic operations example slightly so that the slots all return the results of computing the product, quotient, sum, or difference. Then the signal itself can return a value based on these results to be printed:
This example program will output A more interesting signal result would be the maximum of the values returned by any slot. To do this, we create a custom combiner that looks like this: template<typename T> struct maximum { typedef T result_type; template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const { // If there are no slots to call, just return the // default-constructed value if (first == last) return T(); T max_value = *first++; while (first != last) { if (max_value < *first) max_value = *first; ++first; } return max_value; } }; The We actually use this new function object type by installing it as a combiner for our signal. The combiner template argument follows the signal's calling signature:
Now we can connect slots that perform arithmetic functions and use the signal: sig. The output of this program will be In other cases we might want to return all of the values computed by the slots together, in one large data structure. This is easily done with a different combiner: template<typename Container> struct aggregate_values { typedef Container result_type; template<typename InputIterator> Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); } }; Again, we can create a signal with this new combiner:
The output of this program will contain 15, 8, 1.6667, and 2. It
is interesting here that
the first template argument for the The input iterators passed to the combiner transform dereference
operations into slot calls. Combiners therefore have the option to
invoke only some slots until some particular criterion is met. For
instance, in a distributed computing system, the combiner may ask
each remote system whether it will handle the request. Only one
remote system needs to handle a particular request, so after a
remote system accepts the work we do not want to ask any other
remote systems to perform the same task. Such a combiner need only
check the value returned when dereferencing the iterator, and
return when the value is acceptable. The following combiner returns
the first non-NULL pointer to a struct DistributeRequest { typedef FulfilledRequest* result_type; template<typename InputIterator> result_type operator()(InputIterator first, InputIterator last) const { while (first != last) { if (result_type fulfilled = *first) return fulfilled; ++first; } return 0; } }; Slots aren't expected to exist indefinately after they are connected. Often slots are only used to receive a few events and are then disconnected, and the programmer needs control to decide when a slot should no longer be connected. The entry point for managing connections explicitly is the
boost::signals::connection c = sig. Slots can be temporarily "blocked", meaning that they will be
ignored when the signal is invoked but have not been disconnected. The
boost::signals::connection c = sig. The
{
boost::signals::scoped_connection c = sig.
One can disconnect slots that are equivalent to a given function
object using a form of the
Boost.Signals can automatically track the lifetime of objects involved in signal/slot connections, including automatic disconnection of slots when objects involved in the slot call are destroyed. For instance, consider a simple news delivery service, where clients connect to a news provider that then sends news to all connected clients as information arrives. The news delivery service may be constructed like this:
Clients that wish to receive news updates need only connect a
function object that can receive news items to the
struct NewsMessageArea : public MessageArea
{
public:
// ...
void displayNews(const NewsItem& news) const
{
messageText = news.text();
update();
}
};
// ...
NewsMessageArea newsMessageArea = new NewsMessageArea(/* ... */);
// ...
deliverNews.
However, what if the user closes the news message area,
destroying the struct NewsMessageArea : public MessageArea, public boost::signals::trackable { // ... }; At this time there is a significant limitation to the use of
Warning: User-defined function objects and function
objects from other libraries (e.g., Boost.Function or Boost.Lambda)
do not implement the required interfaces for Signal/slot disconnections occur when any of these conditions occur:
These events can occur at any time without disrupting a signal's calling sequence. If a signal/slot connection is disconnected at any time during a signal's calling sequence, the calling sequence will still continue but will not invoke the disconnected slot. Additionally, a signal may be destroyed while it is in a calling sequence, and which case it will complete its slot call sequence but may not be accessed directly. Signals may be invoked recursively (e.g., a signal A calls a slot B that invokes signal A...). The disconnection behavior does not change in the recursive case, except that the slot calling sequence includes slot calls for all nested invocations of the signal. Slots in the Boost.Signals library are created from arbitrary
function objects, and therefore have no fixed type. However, it is
commonplace to require that slots be passed through interfaces that
cannot be templates. Slots can be passed via the
The Signals can be used to implement flexible Document-View
architectures. The document will contain a signal to which each of
the views can connect. The following class Document { public: typedef boost::signal<void (bool)> signal_t; typedef boost::signals::connection connection_t; public: Document() {} connection_t connect(signal_t::slot_function_type subscriber) { return m_sig.connect(subscriber); } void disconnect(connection_t subscriber) { subscriber.disconnect(); } void append(const char* s) { m_text += s; m_sig(true); } const std::string& getText() const { return m_text; } private: signal_t m_sig; std::string m_text; }; Next, we can define a class View { public: View(Document& m) : m_document(m) { m_connection = m_document.connect(boost::bind(&View::refresh, this, _1)); } virtual ~View() { m_document.disconnect(m_connection); } virtual void refresh(bool bExtended) const = 0; protected: Document& m_document; private: Document::connection_t m_connection; }; Finally, we can begin to define views. The
following class TextView : public View { public: TextView(Document& doc) : View(doc) {} virtual void refresh(bool bExtended) const { std::cout << "TextView: " << m_document.getText() << std::endl; } }; Alternatively, we can provide a view of the document
translated into hex values using the class HexView : public View { public: HexView(Document& doc) : View(doc) {} virtual void refresh(bool bExtended) const { const std::string& s = m_document.getText(); std::cout << "HexView:"; for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) std::cout << ' ' << std::hex << static_cast<int>(*it); std::cout << std::endl; } }; To tie the example together, here is a
simple int main(int argc, char* argv[]) { Document doc; TextView v1(doc); HexView v2(doc); doc.append(argc == 2 ? argv[1] : "Hello world!"); return 0; } The complete example source, contributed by Keith MacDonald,
is available in Part of the Boost.Signals library is compiled into a binary
library that must be linked into your application to use
Signals. Please refer to
the Getting Started
guide. You will need to link against the
|