stop.execute; // Some inspired songwriter has passed some lyrics std::string s="What a beautiful morning..."; record.set_function boost::bind&tape_recorder::record,&tr,s; record.exe
Trang 1function f_ in a Boolean context If the function doesn't contain a target, a function or function object, this yields false, which means that we cannot invoke
it This is tested in the implementation of execute Here's a sample program that uses our new class:
int main() {
tape_recorder tr;
command play(boost::bind(&tape_recorder::play,&tr));
command stop(boost::bind(&tape_recorder::stop,&tr));
command forward(boost::bind(&tape_recorder::stop,&tr));
command rewind(boost::bind(&tape_recorder::rewind,&tr));
command record;
// Invoked from some GUI control
if (play.enabled()) {
play.execute();
}
// Invoked from some scripting client
stop.execute();
// Some inspired songwriter has passed some lyrics
std::string s="What a beautiful morning ";
record.set_function(
boost::bind(&tape_recorder::record,&tr,s));
record.execute();
}
To create the concrete commands, we use Boost.Bind to create function objects that, when invoked through the function call operator, calls the correct member function of tape_recorder These function objects are self-contained; they are nullary function objects, meaning that they can be directly invoked without passing any arguments, which is what a boost::function<void()> expects In other words, the following code snippet creates a function object that invokes the member function play on the instance of tape_recorder that it is configured with
boost::bind(&tape_recorder::play,&tr)
Trang 2Normally, we wouldn't be able to store the returned function object from the call to bind, but because Boost.Function is compatible with any function object, this is possible
boost::function<void()> f(boost::bind(&tape_recorder::play,&tr));
Notice that the class also supports calling record, which takes an argument of type const std::string&, because of the member function
set_function Because the function object must be nullary, we need to bind the context so that record still gets its argument That, of course, is a job for binders Thus, before calling record, we create a function object that contains the string to be recorded
std::string s="What a beautiful morning ";
record.set_function(
boost::bind(&tape_recorder::record,&tr,s));
Executing the function object stored in record passes the string to
tape_recorder::record, invoked on the tape_recorder instance tr With Boost.Function and Boost.Bind, it is possible to achieve the decoupling that makes it possible for the invoking code to know nothing about the code being invoked It's immensely useful to combine these two libraries in this way Having shown you the command class, it's time for me to come clean All you really need, because of the power of Boost.Function, is the following:
typedef boost::function<void()> command;
Using Boost.Lambda with Boost.Function
Just as Boost.Function is compatible with the function objects that are created by Boost.Bind, it also supports Boost.Lambda, which also creates function objects Any function object that you create with the Lambda library is compatible with the corresponding boost::function We have covered some ground on binding
in the previous section, and the main difference is that it is possible to do even more with Boost.Lambda We can create these small, unnamed functions at ease, and store them in instances of boost::function for subsequent invocation
We have covered lambda expressions in the previous chapterany of the examples that were provided there produced function objects that could be stored in an
instance of function The combination of function and libraries that create function objects is extremely powerful
Trang 3Thinking About the Cost
There's no such thing as a free lunch, as the saying goes, and this certainly applies
to Boost.Function, too There are some disadvantages of using Boost.Function compared to using function pointers, most notably the increase in size A function pointer obviously occupies the space of one function pointer (duh!), whereas an instance of boost::function is three times as large This can be an issue when there is a very large number of callbacks Function pointers are also slightly more efficient when invoked, because where a function pointer is invoked directly, Boost.Function may require two calls through function pointers Finally, there may
be cases where the backward compatibility with C libraries makes function
pointers the only choice
Although these are potential disadvantages of Boost.Function, it is not very often that these are real-world issues The extra size is still very small, and the overheard
of the (potential) extra call through a function pointer usually comes at a very small cost indeed compared to the time it takes to actually perform the
computations of the target function It should be the rare situation that mandates using function instead of Boost.Function The great advantages and the flexibility that is gained through using the library easily outweigh these costs
Under the Hood
As always, it is valuable to understand at least the basics of how a library works
We shall consider the three cases of storing and invoking a function pointer, a pointer to member function, and a function object These three are all different To see exactly how Boost.Function works, just look at the source codewe are going to
do things a bit differently, in an attempt to clearly show how different versions of such beasts demand slightly different approaches We will also have a class with different requirements, namely that when calling a member function, a pointer to
an instance of the class must be passed to the constructor of function1 (which
is the name of our class) function1 supports functions taking exactly one
argument A relaxation of the requirements compared to Boost.Function is that even for member functions, only the type of the result and the argument need to be supplied This is a direct consequence of the requirement that the constructor must
be passed a pointer to an instance of the class for which the member is to be called (the type is automatically deduced)
The approach we shall take is to create a parameterized base class that declares a virtual function for the function call operator; then, three classes are derived from
Trang 4this base class for the different forms of function invocations that we are to
support These classes take care of all the work, while another, function1, decides which of these concrete classes to instantiate depending on the arguments
to the constructor Here is the base class for the invokers, invoker_base
template <typename R, typename Arg> class invoker_base {
public:
virtual R operator()(Arg arg)=0;
};
Next, we begin with the definition of function_ptr_invoker, which is a concrete invoker that derives publicly from invoker_base Its purpose is to invoke free functions The class also accepts two types, the return type and the argument type, and these are used for the constructor, which takes a function
pointer as argument
template <typename R, typename Arg> class function_ptr_invoker
: public invoker_base<R,Arg> {
R (*func_)(Arg);
public:
function_ptr_invoker(R (*func)(Arg)):func_(func) {}
R operator()(Arg arg) {
return (func_)(arg);
}
};
This class template can be used to call any free function that accepts one argument The function call operator simply invokes the function stored in func_ with the parameter it is given Note the (admittedly strange) line that declares a variable to store the function pointer
R (*func_)(Arg);
You can make that a little plainer using a typedef
typedef R (*FunctionT)(Arg);
FunctionT func_;
Next, we need a class template that can handle member function calls Remember that it should also require a pointer to an instance of the class type when
Trang 5constructed, which is different from the way that Boost.Function works It does save us some typing, because the compiler, not the programmer, deduces the type
of the class
template <typename R, typename Arg, typename T>
class member_ptr_invoker :
public invoker_base<R,Arg> {
R (T::*func_)(Arg);
T* t_;
public:
member_ptr_invoker(R (T::*func)(Arg),T* t)
:func_(func),t_(t) {}
R operator()(Arg arg) {
return (t_->*func_)(arg);
}
};
This class template is very similar to the version for free function pointers It
differs from the previous one in that the constructor stores a member function pointer and a pointer to an object and the function call operator invokes the
member function (func_) on that object (t_)
Finally, we need a version that is compatible with function objects This is actually the easiest implementation of all, at least when defining it our way By using a single template parameter, we just state that the type T must indeed be a function object, because we intend to invoke it as such Enough said
template <typename R, typename Arg, typename T>
class function_object_invoker :
public invoker_base<R,Arg> {
T t_;
public:
function_object_invoker(T t):t_(t) {}
R operator()(Arg arg) {
return t_(arg);
}
};
Now that we have these building blocks in place, all that remains is to put the pieces together in our version of boost::function, the function1 class
Trang 6We want a way to detect which kind of invoker to instantiate We can then save it
in a pointer to invoker_base The trick here is to provide constructors that are capable of detecting which type of invoker is correct for the supplied arguments It's just overloading, with a little twist involving parameterizing two of the
constructors
template <typename R, typename Arg> class function1 {
invoker_base<R,Arg>* invoker_;