Beyond the C++ Standard Library: An Introduction to Boost -- Library 10.2 Usage(下)

Lambda 表达式中的控制结构

我们已经看到强大的 lambda 表达式可以很容易地创建,但是许多编程上的问题需要我们可以表示条件,在C++中我们使用 if-then-else, for, while, 等等。在 Boost.Lambda 中有所有的C++控制结构的 lambda 版本。要使用选择语句,if switch, 就分别包含头文件 "boost/lambda/if.hpp" "boost/lambda/switch.hpp"。要使用循环语句,while, do, for, 就包含头文件 "boost/lambda/loops.hpp". 关键字不能被重载,所以语法与你前面使用过的有所不同,但是也有很明显的关联。作为第一个例子,我们来看看如何在 lambda 表达式中创建一个简单的 if-then-else 结构。格式是 if_then_else(条件, then-语句, else-语句)。还有另外一种语法形式,即 if_(条件)[then-语句].else_[else-语句]

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
int main() {
  using namespace boost::lambda;
  std::vector<std::string> vec;
  vec.push_back("Lambda");
  vec.push_back("expressions");
  vec.push_back("really");
  vec.push_back("rock");
  std::for_each(vec.begin(),vec.end(),if_then_else(
    bind(&std::string::size,_1)<=6u,
    std::cout << _1 << '/n',
    std::cout << constant("Skip./n")));
  std::for_each(vec.begin(),vec.end(),
    if_(bind(&std::string::size,_1)<=6u) [
      std::cout << _1 << '/n'
    ]
    .else_[
      std::cout << constant("Skip./n")
    ] );
}

如果你是从本章开头一直读到这的,你可能会觉得上述代码非常好读;但如果你是跳到这来的,就可能觉得惊讶了。控制结构的确增加了阅读 lambda 表达式的复杂度,它需要更长一点的时间来掌握它的用法。当你掌握了它以后,它就变得很自然了(编写它们也一样!)。采用哪一种格式完全取决于你的爱好;它们做得是同一件事。

在上例中,我们有一个 string vector,如果 string 元素的大小小于等于6,它们就被输出到 std::cout; 否则,输出字符串 "Skip"。在这个 if_then_else 表达式中有一些东西值得留意。

if_then_else(
  bind(&std::string::size,_1)<=6u,
  std::cout << _1 << '/n',
  std::cout << constant("Skip./n")));

首先,条件是一个谓词,它必须是一个 lambda 表达式!其次,then-语句必须也是一个 lambda 表达式!第三,else-语句必须也是一个 lambda 表达式!头两个都很容易写出来,但最后一个很容易忘掉用 constant 来把字符串("Skip/n")变成一个 lambda 表达式。细心的读者会注意到例子中使用了 6u, 而不是使用 6, 这是为了确保执行的是两个无符号类型的比较。这样做的原因是,我们使用的是非常深的嵌套模板,这意味着如果这样一个 lambda 表达式引发了一个编译器警告,输出信息将会非常、非常长。你可以试一下去掉这个 u,看看你的编译器会怎样!你将看到一个关于带符号类型与无符号类型比较的警告,因为 std::string::size 返回一个无符号类型。

控制结构的返回类型是 void, 除了 if_then_else_return, 它调用条件操作符。让我们来仔细看看所有控制结构,从 if switch 开始。记住,要使用 if-结构,必须包含 "boost/lambda/if.hpp"。对于 switch, 必须包含 "boost/lambda/switch.hpp"。以下例子都假定名字空间 boost::lambda 中的声明已经通过using声明或using指令,被带入当前名字空间。

(if_then(_1<5,
  std::cout << constant("Less than 5")))(make_const(3));

if_then 函数以一个条件开始,后跟一个 then-部分;在上面的代码中,如果传给该 lambda 函数的参数小于5 (_1<5), "Less than 5" 将被输出到 std::cout. 你会看到如果我们用数值3调用这个 lambda 表达式,我们不能直接传递3,象这样。

(if_then(_1<5,std::cout << constant("Less than 5")))(3);

这会引起一个编译错误,因为3是一个 int, 而一个类型 int (或者任何内建类型)的左值不能被 const 限定。因此,我们在这里必须使用工具 make_const,它只是返回一个对它的参数的 const 引用。另一个方法是把整个 lambda 表达式用于调用 const_parameters, 象这样:

(const_parameters(
  if_then(_1<5,std::cout << constant("Less than 5"))))(3);

const_parameters 对于避免对多个参数分别进行 make_const 非常有用。注意,使用该函数时,lambda 表达式的所有参数都被视为 const 引用。

现在来看另一种语法的 if_then

(if_(_1<5)
  [std::cout << constant("Less than 5")])(make_const(3));

这种写法更类似于C++关键字,但它与 if_then 所做的完全一样。函数 if_ (注意最后的下划线)后跟括起来的条件,再后跟 then-语句。重复一次,选择哪种语法完全取决于你的口味。

现在,让我们来看看 if-then-else 结构;它们与 if_then 很相似。

(if_then_else(
  _1==0,
  std::cout << constant("Nothing"),
  std::cout << _1))(make_const(0));
(if_(_1==0)
  [std::cout << constant("Nothing")].
  else_[std::cout << _1])(make_const(0));

使用第二种语法增加 else-部分时,要留意 else_ 前面的点。

lambda 表达式的返回类型是 void, 但是有一个版本会返回一个值,它使用条件操作符。对于这种表达式的类型有一些不平常的规则(我在这里略过它们,你可以在 Boost.Lambda 的在线文档或 C++ 标准[§5.16] 找到详细的说明)。这里有一个例子,返回值被赋给一个变量,就象你在使用条件操作符一样。

int i;
int value=12;
var(i)=(if_then_else_return
  (_1>=10,constant(10),_1))(value);

这个结构没有第二种语法。这些就是 if-then-else, 我们再看看 switch-语句,它与标准C++ switch有些不同。

(switch_statement
  _1,
  case_statement<0>
    (var(std::cout) << "Nothing"),
  case_statement<1>
    (std::cout << constant("A little")),
  default_statement
    (std::cout << _1))
  )(make_const(100));

switch_statement 的调用从条件变量开始,即我们这里的 _1, lambda 表达式的第一个参数。它后跟(最多九个)表现为整型的 case 常量;它们必须是整型的常量表达式。我们提供了两个这样的常量,0 1 (注意,它们可以是任何可作为整型类型的值)。最手,我们加一个可选的 default_statement, 它在 _1 不匹配任何一个常量时被执行。注意,在每一个 case 常量后都隐式地增加了一个 break-语句,所以无需从 switch 显式退出(这对于代码的维护是一件好事[6])

[6] Spokesmen of fall-through case-statements; please excuse this blasphemy.

现在我们来看循环语句,for, while, do. 要使用它们中的任意一个,你必须首先包含头文件 "boost/lambda/loops.hpp"Boost.Lambda中与C++while 相对应的是 while_loop

int val1=1;
int val2=4;
(while_loop(_1<_2,
  (++_1,std::cout << constant("Inc.../n"))))(val1,val2);

while_loop 语句执行到条件为 false 止;这里的条件是 _1<_2, 后跟循环体,即表达式 ++_1,std::cout << constant("Inc.../n"). 当然,条件和循环体本身必须是有效的 lambda 表达式。另一种语法更接近C++语法,就象 if_ 那样。

int val1=1;
int val2=4;
(while_(_1<_2)
  [++_1,std::cout << constant("Inc.../n")])(val1,val2);

格式是 while_(条件)[子语句], 它可以节省不少输入…但是我个人认为对于 while 而言函数调用语法更容易读,虽然我(不太合理)认为 if_ if_then(...) 容易看。从外表看,do_while_loop while_loop 非常相似,但它的子语句至少被执行一次(不象 while, 它的条件在每次执行后求值)

(do_while_loop(_1!=12,std::cout <<
  constant("I'll run once")))(make_const(12));

另一种语法是

(do_[std::cout <<
  constant("I'll run once")].while_(_1!=12))(make_const(12));

最后是 for 循环的对应,for_loop. 在以下例子中,使用了一个命名的延期变量来让 lambda 表达式更可读。我们在前面介绍 constant var 时已经介绍过延期变量。命名的延期变量用于避免重复为常量和变量敲入 constant var 。它们对你要用的东西进行命名并稍后可被引用。常用的循环格式是 for_loop(init-语句, 条件, 表达式, 语句),即它与一个普通语句相似,但它是函数的一部分(参数)

int val1=0;
var_type<int>::type counter(var(val1));
(for_loop(counter=0,counter<_1,++counter,var(std::cout)
  << "counter is " << counter << "/n"))(make_const(4));

采用另一种语法,语句被分为初始化、条件和表达式。

(for_(counter=0,counter<_1,++counter)[var(std::cout)
  << "counter is " << counter << "/n"])(make_const(4));

这个例子把延期变量 counter 初始化为0,条件为 counter<_1, 表达式为 ++counter.

总结一下本节的控制结构。对于我遇到并使用 lambda 表达式来解决的多数问题,事实上也可以不用它们,但有些时候,它们的确真的是救命者。对于选择哪一种语法版本,最好的办法可能是两种都用,然后感觉一下哪一种最适合于你。要注意的一点是,使用 switch 和循环结构时,lambda 表达式很快就会变得很大,如果你还不能熟练使用这个库,你将很难弄懂这样的表达式。这时应当小心,如果一个表达式看起来让你的程序员同事难以分析,不要考虑使用独立的函数对象(或者让他们更加熟练地使用 Boost.Lambda)

Lambda 表达式中的类型转换

在 lambda 表达式中有四种特殊的"转型操作符"[7] 来进行类型的转换:ll_dynamic_cast, ll_static_cast, ll_reinterpret_cast, 和 ll_const_cast. 这些名字与对应的C++关键字不一样,因为它们不能被重载。要使用这些类型转换,就要包含头文件 "boost/lambda/casts.hpp". 这些函数与相对应的C++转型操作符用法类似;它们带一个显式的模板参数,即要转成的目标类型,以及一个隐式的模板参数,即源类型。在我们的第一个例子中,我们将使用两个类,名为 basederived. 我们将创建两个指向 base 的指针,一个指向 base 实例,另一个指向 derived 实例。然后我们尝试使用 ll_dynamic_cast 来从这两个指针分别获得一个 derived*

[7] 技术上,它们是返回函数对象的模板函数。

#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/bind.hpp"

class base {
public: virtual ~base() {} void do_stuff() const { std::cout << "void base::do_stuff() const/n"; } };

class derived : public base { public: void do_more_stuff() const { std::cout << "void derived::do_more_stuff() const/n"; } };

int main() { using namespace boost::lambda; base* p1=new base; base* p2=new derived; derived* pd=0; (if_(var(pd)=ll_dynamic_cast<derived*>(_1)) [bind(&derived::do_more_stuff,var(pd))]. else_[bind(&base::do_stuff,*_1)])(p1); (if_(var(pd)=ll_dynamic_cast<derived*>(_1)) [bind(&derived::do_more_stuff,var(pd))]. else_[bind(&base::do_stuff,*_1)])(p2); }

main 中,我们做的第一件事情是创建 p1p2; p1 指向一个 base, 而 p2 则指向一个 derived. 在第一个 lambda 表达式中,被赋值的 pd 变成了条件;它被隐式转换为 bool, 如果它为 true, then-部分被求值。这里,我们 bind 到成员函数 do_more_stuff. 如果 ll_dynamic_cast 失败了,延期变量 pd 将为 0, 则 else-部分被执行。因此,在我们例子中,lambda 表达式的第一次执行将调用 base 上的 do_stuff ,而第二次执行则调用 derived 上的 do_more_stuff ,运行这个程序的输出如下。

void base::do_stuff() const
void derived::do_more_stuff() const

注意,在这个例子中,参数 _1 被解引用,但这并不是必须的;如果需要它会隐式地完成。如果一个 bind 表达式的某个参数必须总是一个指针类型,你可以自己强制对它解引用。否则,把这件杂事留给 Boost.Lambda.

ll_static_cast 对于避免警告非常有用。不要用它来抑制重要的信息,但可以用来减少噪音。在前面的一个例子中,我们创建了一个 bind 表达式来求一个 std::string 的长度(使用 std::string::size)并将该长度与另一个整数进行比较。std::string::size 的返回类型是一个无符号类型,把它与一个有符号整数进行比较(这很常见),编译器会产生一个警告,说有符号与无符号的比较是危险的动作。但是,因为这发生在一个 lambda 表达式中,编译器忠实地跟踪到问题的根源,告诉你某个嵌套模板的某部分应对此严重问题负责。结果是一个非常长的警告信息,由于低信噪比的原因,它可能会掩盖了其它的问题。在泛型代码中,这有时会成为问题,因为所使用的类型不在我们的控制之内。因而,在评估过可能潜在的问题后,你常会发现使用 ll_static_cast 来抑制不想要的警告是有好处的。以下例子包含了示范这种行为的代码。

#include <iostream>
#include <string>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/bind.hpp"

template <typename String,typename Integral>
void is_it_long(const String& s,const Integral& i) { using namespace boost::lambda; (if_then_else(bind(&String::size,_1)<_2, var(std::cout) << "Quite short.../n", std::cout << constant("Quite long.../n")))(s,i); }
int main() {
std::string s="Is this string long?"; is_it_long(s,4u); is_it_long(s,4); }

泛型函数 is_it_long (请不要关注它是一个有点过于做作的例子)用一个 Integral 类型的常数变量引用来执行一个 lambda 表达式。现在,这个类型是有符号的还是无符号的并不在我们的控制之内,因此很有机会某个用户会不小心引发了一个非常冗长的警告,正如这个例子所示范的,因为用了一个有符号整数调用 is_it_long

is_it_long(s,4);

确保用户不会无意地引发此事(除了要求只能使用无符号类型)的唯一办法是让这个参数变成无符号整数类型,不管它原来是什么。这正是 ll_static_cast 的工作,因此我们把函数 is_it_long 改成这样:

template <typename String,typename Integral>
void is_it_long(const String& s,const Integral& i) {
  using namespace boost::lambda;
  (if_then_else(bind(&String::size,_1)<
    ll_static_cast<typename String::size_type>(_2),
    var(std::cout) << "Quite short.../n",
    std::cout << constant("Quite long.../n")))(s,i);
}

这种情况不会经常发生(至少我没碰到几次),但只要它发生了,这种解决方法就可用。ll_const_castll_reinterpret_cast 的用法与我们已经看到的相似,所以就不再举例了。小心地使用它们,如果没有非常必要的原因(我想不到有什么原因)尽量不要使用 ll_reinterpret_cast 。这是对称的;如果你需要用它,很大机会你做了一些不应该做的事情。

构造与析构

当有必要在 lambda 表达式中创建或销毁对象时,就需要一些特殊的处理和语法。首先,你不可能获取构造函数或析构函数的地址,也就不可能对它们使用标准的 bind 表达式。此外,操作符 newdelete 有固定的返回类型,因此它们对于任何类型都不能返回 lambda 表达式。如果你需要在 lambda 表达式中创建或销毁对象,先要确保包含头文件 "boost/lambda/construct.hpp", 它包含了模板 constructor, destructor, new_ptr, new_array, delete_ptr, 以及 delete_array. 我们来看看如何使用它们,并主要关注 constructornew_ptr, 它们在构造对象时是最常用的。

我们的第一个例子是一个以智能指针作为元素的容器,我们想在 lambda 表达式中重设智能指针的内容。这通常需要一个对 operator new 的调用;例外的情形是使用了客户化的分配机制,或者是某种工厂方法(factory method)。我们要用 new_ptr 来做,如果你想要或者需要的话,通常也可以在赋值表达式中使用 constructor 。我们两种方法都试一下。我们将定义两个类,basederived, 以及一个 boost::shared_ptr<base>std::map ,它以 std::strings 为索引。在阅读本例中的 lambda 表达式之前,先来一下深呼吸;它们将是你在本章所见到的最复杂的两个 lambda 表达式。虽然复杂,但是要理解它们是干什么的还是很明白的。只是要花你一点时间。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/construct.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/shared_ptr.hpp"

class base { public: virtual ~base() {} };
class derived : public base {
};
int main() {
using namespace boost::lambda;
typedef boost::shared_ptr<base> ptr_type; typedef std::map<std::string,ptr_type> map_type;
map_type m; m["An object"]=ptr_type(new base); m["Another object"]=ptr_type(); m["Yet another object"]=ptr_type(new base);
std::for_each(m.begin(),m.end(), if_then_else(!bind(&ptr_type::get, bind(&map_type::value_type::second,_1)), (bind(&map_type::value_type::second,_1)= bind(constructor<ptr_type>(),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"));
m["Beware, this is slightly tricky"]=ptr_type(); std::cout << "/nHere we go again.../n";
std::for_each(m.begin(),m.end(), if_then_else(!bind(&map_type::value_type::second,_1), ((bind(static_cast<void (ptr_type::*)(base*)> (&ptr_type::reset<base>), bind(&map_type::value_type::second,_1), bind(new_ptr<base>()))), 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")); }

你都看懂了,是吗?以防万一有些混乱,我来解释一下在这个例子中发生了什么。首先,这两个 lambda 表达式做的是同一件事情。它们对 std::map 中的每一个当前为 null 的元素设置有效的指针。以下是程序运行的输出:

"An object" already has a valid pointer.
"Another object" already has a valid pointer.
"Yet another object" already has a valid pointer.
// 译注:前面这三行在原书中有重复,共六行,与程序运行结果不符

Here we go again... "An object" already has a valid pointer. "Another object" already has a valid pointer. Created a new derived for "Beware, this is slightly tricky". "Yet another object" already has a valid pointer.

输出显示我们设法把有效的对象放入 map 的每一个元素,但是这是如何办到的呢?

两个表达式完成了相同的工作,但各自有不同的方法。从第一个开始,我们来把这个 lambda 表达式切开来看看它是如何工作的。当然,第一部分是条件,它很普通:[8]

[8] 它还可以更简单,我们即将会看到。

!bind(&ptr_type::get,bind(&map_type::value_type::second,_1))

这样更容易看了,对吗?从最里面的 bind 开始读这个表达式,它告诉我们将绑定到成员 map_type::value_type::second (它是一个 ptr_type),然后我们再绑定成员函数 ptr_type::get (它返回 shared_ptr 的指向物),最后我们对整个表达式执行 operator!. 由于指针可以隐式转换为 bool, 因此这是一个有效的布尔表达式。看完条件部分,我们来看 then-部分。

bind(&map_type::value_type::second,_1)=
  bind(constructor<ptr_type>(),
    bind(new_ptr<derived>())),

这里有三个 bind 表达式,第一个(我们从最左边开始,因为这个表达式有一个赋值)取出成员 map_type::value_type::second, 它是一个智能指针。这就是我们要赋给它一个新的 derived那个值。第二和第三个表达式是嵌套的,所以我们从里向外读。里面的 bind 负责堆上的一个 derived 实例的缺省构造,我们再在它的结果之上 bind 一个对 ptr_type (智能指针类型)constructor 的调用,然后把它的结果赋值(使用普通的赋值符)给最先的那个 bind 表达式。然后,我们给这个 then-部分再加一个表达式,打印出一个简短的信息和元素的键值。

var(std::cout) << "Created a new derived for /"" <<
  bind(&map_type::value_type::first,_1) << "/"./n")

最后,我们加上语句的 else-部分,它打印出元素的键值和一些文字。

var(std::cout) << "/"" <<
  bind(&map_type::value_type::first,_1)
  << "/" already has a valid pointer./n"));

把这个表达式分开来读,很明显它们并不是那么复杂,虽然整个看起来很可怕。很重要的一点是,缩进和分离这些代码可以更容易阅读。我们可以写出类似的表达式来完成这件工作,它与这一个版本非常不同,更难阅读,但是它的效率稍高一些。这里要注意的是,通常都会有好几种方法来写 lambda 表达式,就象其它编程问题的情况一样。在写代码之前应该多想一下,因为你的选择会影响最终结果的可读性。作为比较,以下是我提到的另一个版本:

std::for_each(m.begin(),m.end(),
  if_then_else(!bind(&map_type::value_type::second,_1),
    ((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"));

这不是好的代码,这些代码由于类型转换和复杂的嵌套 binds 而变得混乱,与前面那个版本相比,这个版本很容易使我们偏离主要逻辑。为了弄明白它,我们再来把这个表达式切开成几个部分。首先,我们有条件部分,它很简单(这个表达式中没有其它东西!);我们利用对 shared_ptr 的了解,它告诉我们有一个到 bool 的隐式转换可用。因此我们可以去掉在前一个版本中使用的到成员函数 getbind

!bind(&map_type::value_type::second,_1)

这个条件部分与原先的表达式一样工作。接下来的部分是:

bind(static_cast<void (ptr_type::*)(base*)>
  (&ptr_type::reset<base>),
  bind(&map_type::value_type::second,_1),
  bind(new_ptr<derived>()))

这真的很难分析,所以我们先避开它。我们不使用赋值,而是直接使用成员函数 reset, 它不仅是泛化的而且还是重载的。因此我们需要执行 static_cast 来告诉编译器我们要用那个版本的 reset 。这里主要是 static_cast 让表达式变得复杂,但是从最里面的表达式开始分析,我们就可以弄懂它。我们绑定一个调用到 operator new, 创建一个 derived 实例,然后我们把结果绑定到智能指针(通过成员 map_type::value_type::second), 然后再绑定到 shared_ptr 的成员函数 reset. 结果是,对元素中的智能指针调用 reset ,参数是一个新构造的 derived 实例。虽然我们在前一个例子中也完成了同样的工作,但这个版本更难以理解。

要记住,通常有一些因素让 lambda 表达式更容易或者更难阅读和理解,要认真考虑这些因素并尽可能选择容易的形式。这对于获得这个库所提供的能力是必要的,而且它会影响到你后面的程序员。

抛出及捕获异常

我们已经来到本章的最后一节,讨论 lambda 表达式中的异常处理。如果你对这个话题的反应是怀疑在 lambda 表达式中是否需要异常处理,这就和我的第一感觉一样了。但是,这可能还不是你的想法。你写过在处理数据的循环中执行局部异常处理的代码吗?是的,手写的循环可以避免使用 Boost.Lambda 库,因此把异常处理放入 lambda 表达式是很自然的。

要使用 Boost.Lambda 的异常处理工具,就要包含头文件 "boost/lambda/exceptions.hpp". 我们来重用一下前面看到的类 basederived ,并象我们前面做过的那样执行 dynamic_cast ,不过这次我们要对引用执行转型而不是对指针,这意味着如果失败的话,dynamic_cast 将抛出一个异常。这使得这个例子比前面那个更直观,因为我们不需要再使用 if 语句。

#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>( bind(&base::do_stuff,_1))))(p2); }

这些表达式示范了把一个表达式包装到一个对 try_catch 的调用。try_catch 的通常形式为:

try_catch(expression,
  catch_exception<T1>(expression),
  catch_exception<T2>(expression,
  catch_all(expression))

在这段例子代码中,表达式对 derived& 使用 dynamic_cast。第一个转型由于 p1 指向一个 base 实例而失败;第二个转型则由于 p2 指向一个 derived 实例而成功。请留意对占位符的解引用(*_1)。这是必须的,因为我们是传送指针参数给表达式的,而 dynamic_cast 要求的是对象或引用。如果你需要 try_catch 处理几种类型的异常,就要确保把最特化的类型放在前面,就象普通的异常处理代码一样。[9]

9] 否则,一个更为通用的类型将会匹配该异常而不能查找到更为特殊的类型。具体详情请参考你喜欢的C++书籍。

如果我们想访问捕获的异常,可以用一个特别的占位符,_e. 当然,在 catch_all 中不能这样做,就象在 catch (...) 中没有异常对象一样。继续前面的例子,我们可以打印出令 dynamic_cast 失败的原因,象这样:

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);

在处理一个派生自 std::exception 的异常类型时——这是很常见的情形——你可以绑定到虚拟成员函数 what, 就象这里所示范的那样。

但是有时候,你不想捕获异常,而是想抛出一个异常。这可以通过函数 throw_exception 来实现。因为你需要创建一个异常对象用来抛出,所以从一个 lambda 表达式中抛出异常通常还要使用 constructor 。下面这个例子定义了一个异常类,some_exception, 它公有派生自 std::exception, 如果 lambda 表达式的参数为 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"; } }

运行这段程序将产生以下输出:

Throw an exception here.
Caught exception, "Somewhere, something went terribly wrong."

最有趣的地方是抛出异常的那段代码。

throw_exception(
  bind(constructor<some_exception>(),
    constant("Somewhere, something went /
      terribly wrong."))

throw_exception 的参数是一个 lambda 表达式。这个例子中,它被创建并绑定到对 some_exception 构造函数的调用,我们把一个字符串作为 what 参数传给该构造函数。

这就是在 Boost.Lambda 中进行异常处理的全部内容。永远记住,要小心谨慎地使用这些工具,它们可以让生活更轻松或更困难,这取决于你能否很好地并明智地使用它们[10] 。抛出并处理异常在你的 lambda 表达式中并不常见,但有些时候还是需要的。

 [10] 小心应了这句格言,"如果你只有锤子,那么所有东西看起来都象钉子"。

 

Lambda 总结

以下情形时使用 Lambda :

  • 你不想创建一个简单的函数对象

  • 你需要在调用函数时调整参数顺序或 arity 

  • 你想就地创建与标准一致的函数对象

  • 你需要灵活并可读的谓词

上述原因只是值得使用本库的几种情形。虽然多数情况下,它会与标准库算法一起用,至少部分原因是由于在其它库(就算是 Boost 库)中这样的设计还不多见。通过函数对象来进行算法配置的需要并不能验证本库的有效性,离完全弄清楚它在哪些地方可以带来好处还有很长一段距离。思考一下这个库可能的应用,一定会提高你当前的设计。

Boost.Lambda 是我最喜欢的库之一,主要是因为它提供语言本身没有的很多可用的功能。要象STL在全世界所有程序员的心中一样,它还缺少一些东西。要让算法高效地工作,还需要一些函数对象以外的东西。这正是 Boost.Lambda 的推动力,它的丰富特性带来了真正简练的编程风格。有许多地方可以使用 lambda 表达式,但还有很多要探究的地方。对于C++而言,这是某种程度上的函数式编程,它是一种仍在探索的编程范式。这些对 Lambda 库的介绍可以推动你继续对它的探究。公平地说,这种语法与"真正"的函数式编程语言相比,显得有点笨拙,而且对于新的用户来说也需要一点时间来习惯它。但是,这对于使用本库的任何C++程序员都有巨大的价值!我希望它也能成为你喜欢的库。

非常感谢 Jaakko Järvi 和 Gary Powell, 他们是本库的作者,并且是C++函数式编程的真正先驱!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值