The innermost bind takes care of the default construction of an instance of derived on the heap, and to the result we bind a constructor call to ptr_type the smart pointer type, which is
Trang 1condition, so we move on to the then-part
bind(&map_type::value_type::second,_1)=
bind(constructor<ptr_type>(),
bind(new_ptr<derived>())),
There are three bind expressions here, the first one (we start reading from the left here, because the expression involves an assignment) extracts the member
map_type::value_type::second, which is the smart pointer This is the value that we assign a new derived to The second and third expressions are nested, so we read them from the inside out The innermost bind takes care of the default construction of an instance of derived on the heap, and to the result we bind a constructor call to ptr_type (the smart pointer type), which is then assigned (using the usual notation for assignment) to the very first bind
expression Then, we add another expression to this then-part, which simply prints out a short message and the element's key
var(std::cout) << "Created a new derived for \"" <<
bind(&map_type::value_type::first,_1) << "\".\n")
Finally, we add the else-part of the statement, which prints out the key of the
element and some text
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
When decomposing the expressions, it's clear that they're not really that complex, although looking at the whole thing can be quite intimidating It's important to indent and separate the code so that reading becomes intuitive We can write a similar expression for accomplishing our task, in a version that's quite different from this one but is much harder to read, although it is slightly more efficient The thing to note here is that there are often several ways of attacking the problem of writing lambda expressions, just as is the case with other programming problems
It makes sense to apply some extra thought before writing, because the choices substantially affect the readability of the end result For comparison, here's the other version I mentioned:
std::for_each(m.begin(),m.end(),
if_then_else(!bind(&map_type::value_type::second,_1),
Trang 2((bind(static_cast<void (ptr_type::*)(base*)>
(&ptr_type::reset<base>),
bind(&map_type::value_type::second,_1),
bind(new_ptr<derived>()))),
var(std::cout) << "Created a new derived for \"" <<
bind(&map_type::value_type::first,_1) << "\".\n"),
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
This is not as nice, because the code is cluttered with casts and complicated nested binds, and we move away from the actual logic more than the previous version did To understand it, let's again decompose the expressions to their constituent parts First, we have the condition, which is actually simplified (nothing else is in this expression!); we utilize our knowledge of shared_ptr, which tells us that there is an implicit conversion to bool available We can thus eliminate the bind
to the member function get that we used in the previous expression
!bind(&map_type::value_type::second,_1)
That condition works with the original expression, too The next part is this:
bind(static_cast<void (ptr_type::*)(base*)>
(&ptr_type::reset<base>),
bind(&map_type::value_type::second,_1),
bind(new_ptr<derived>()))
This is arguably too hard to parse, so we should have avoided it in the first place Rather than using assignment, we go directly for the member function reset, which is not only parameterized but also overloaded We thus need to perform a static_cast to tell the compiler which version of reset we are interested in
In this case, it is mainly the static_cast that complicates the reading of the expression, but again starting from the innermost expression, we can work through
it We bind a call to operator new, creating an instance of derived, and to the result we bind the smart pointer (through the member
map_type::value_type::second), to which we bind the shared_ptr member function reset This results in a call to reset for the smart pointer in the element, with the argument being a newly constructed instance of derived Although we've done basically the same thing as in the previous example, this version is much harder to understand
Trang 3Just remember that there can often be alternatives that lead to lambda expressions that are easier or harder to read and understand, so consider the alternatives and choose the easier forms when possible It is imperative to treat the power that this library offers, and the effect it can have on your fellow programmers, with respect
Throwing and Catching Exceptions
We have reached the final section of this chapter, which discusses exception
handling in lambda expressions If your reaction to this topic is to wonder exactly what justifies exception handling code in a lambda expression, that matches my first thoughts fairly well However, it's not as far fetched as you might think
Surely you have written code that performs local exception handling when
processing data in a loop? Well, handwritten loops can be avoided through usage
of the Boost.Lambda library, so moving that exception handling into lambda
expressions is quite natural
To use the exception handling facilities of Boost.Lambda, include
"boost/lambda/exceptions.hpp" Let's reuse the classes base and derived that we saw earlier, and perform dynamic_casts almost like we did beforebut this time we will perform casts to references rather than pointers, which means that upon failure, dynamic_cast will throw an exception This makes the example more straightforward than what we did before, because we don't need to use an if statement
#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/exceptions.hpp"
int main() {
using namespace boost::lambda;
base* p1=new base;
base* p2=new derived;
(try_catch(
bind(&derived::do_more_stuff,ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>(bind(&base::do_stuff,_1))))(p1);
(try_catch(
bind(&derived::do_more_stuff,
ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>(
Trang 4bind(&base::do_stuff,_1))))(p2);
}
These expressions reveal that you wrap an expression in a call to TRy_catch The general form of try_catch is
try_catch(expression,
catch_exception<T1>(expression),
catch_exception<T2>(expression,
catch_all(expression))
In the example code, the expressions use dynamic_casts to derived& The first cast fails because p1 points to an instance of base; the second cast succeeds, because p2 points to an instance of derived Note the dereferencing of the
placeholders (*_1) This is required because we are passing pointers as arguments
to the expressions, but the dynamic_casts we're interested in expect objects or references If you need the try_catch to handle several types of exceptions, be sure to put the most specialized types first, just as with regular exception handling code.[9]
[9]
Otherwise, a more general type will match an exception and not find the handler for the more specific type Consult your favorite C++ book for more details on this
If we want to access the actual exception that was caught, we can do so using a special placeholder, _e Of course, one cannot do that in catch_all, just as there is no exception object in a catch ( ) Continuing the preceding
example, we can print the reason for the failed dynamic_cast like so:
try_catch(
bind(&derived::do_more_stuff,ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>
(std::cout << bind(&std::exception::what,_e))))(p1);
When dealing with an exception type derived from std::exceptiona common caseyou can bind to the virtual member function what, as shown here
Sometimes, however, you don't want to catch an exception, but rather throw one This is done via the function throw_exception Because you need to create an exception object to throw, you'll typically use constructor to throw an
exception from inside a lambda expression The following example defines an
Trang 5exception class, some_exception, which inherits publicly from
std::exception, and creates and throws one in a lambda expression if the argument to the expression is true
#include <iostream>
#include <exception>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/exceptions.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/construct.hpp"
#include "boost/lambda/bind.hpp"
class some_exception : public std::exception {
std::string what_;
public:
some_exception(const char* what) : what_(what) {}
virtual const char* what() const throw() {
return what_.c_str();
}
virtual ~some_exception() throw() {}
};
int main() {
using namespace boost::lambda;
try {
std::cout << "Throw an exception here.\n";
(if_then(_1==true,throw_exception(
bind(constructor<some_exception>(),
constant("Somewhere, something went \
terribly wrong.")))))(make_const(true));
std::cout << "We'll never get here!\n";
}
catch(some_exception& e) {
std::cout << "Caught exception, \"" << e.what() << "\"\n";
}
}
Running this program yields the following output:
Throw an exception here
Caught exception, "Somewhere, something went terribly wrong."