基于表达式的回调






4.60/5 (7投票s)
2006年3月12日
2分钟阅读

52081
一种在 STL 容器中提供基于表达式的回调函数的简单方法。
引言
在大多数使用集合的场景中,经常需要遍历集合、执行计算并将结果收集到变量中。本文介绍了一种在 C++ 中实现此目的的简单方法,与最常见的方法不同,它使用容器和表达式模板。本文将使用 STL,但该技术可应用于任何容器。
常见方法
遍历集合最常用的方法是使用 for
循环。编写 for
循环一段时间后会变得繁琐,因此许多语言都提供了一种“for-each”语句,允许遍历集合。虽然 C++ 没有提供这样的语句,但以各种方式很容易实现它。最常见的方法是在一个函数中创建一个 for
循环,该函数接受一个回调函数,如下所示
template <class C> void forEach(C &c, void (*callback)(C::value_type)) { for(C::iterator it = c.begin(); it != c.end(); ++it) { callback(*it); } }
但是,这种方法有一些缺点;它会打断程序员的思路,让他/她思考将回调函数放置在哪里,以及如何从回调函数内部访问局部数据。
表达式模板
表达式模板应运而生:通过使用运算符重载,可以作为 for-each 循环的回调函数使用表示绑定到局部参数的表达式的类。在解释如何编写此类类之前,让我们看一个使用示例
#include <list> #include <iostream> using namespace std; int main() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); int i = 0; Expr< int > x; forEach(l, i, x += i); cout << x; getchar(); return 0; }
正如您所见,使用上面的代码对数字列表求和要容易得多!
工作原理
这是重要的代码行;让我们专注于它
forEach(l, i, x += i);
C++ 没有 lambda 函数或闭包,但它具有运算符重载。因此,在上面的代码中,x
是一个对象,当应用 operator +=
时,它实际上并没有添加任何内容,而是创建了一个函数对象,该对象封装了变量 x
和 i
,并在调用时执行该操作。
类 Expr
,x
是其的一个实例,如下所示
template <class T> class Expr { public: Expr() : m_v(T()) { } ExprAddAssign<T> operator += (T &i) { return ExprAddAssign<T>(m_v, i); } operator T () const { return m_v; } private: T m_v; };
我们可以看到它封装了一个类型为 type T
的值。我们还看到 operator +=
返回类 ExprAddAssign
的一个实例,该类是这个类
template <class T> class ExprAddAssign { public: ExprAddAssign(T &lv, const T &rv) : m_lv(lv), m_rv(rv) { } void operator ()() const { m_lv += m_rv; } private: T &m_lv; const T &m_rv; };
上面的类包含两个绑定:一个用于 rvalue
,一个用于 lvalue
。lvalue
被赋值为 rvalue
的值。函数 'forEach
' 的代码变为
template <class C, class T, class F> void forEach(const C &c, T &v, F &obj) { for(C::const_iterator it = c.begin(); it != c.end(); ++it) { v = *it; obj(); } }
结论
可以编程出各种各样的表达式模板。甚至可以使用类似 Smalltalk 的语法对 if-then-else 结构进行编码。例如
forEach(l, (i > 2).ifTrue(x += i));
我实际上是在寻找在 C++ 中创建 lambda 函数的方法时偶然发现了这个解决方案。我意识到,只要可以将 lambda 参数声明为局部变量,就不需要 lambda 函数了。
我很惊讶 STL 库中没有包含这样一组类。它将为 C++ 提供强大的功能……