65.9K
CodeProject 正在变化。 阅读更多。
Home

基于表达式的回调

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (7投票s)

2006年3月12日

2分钟阅读

viewsIcon

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 += 时,它实际上并没有添加任何内容,而是创建了一个函数对象,该对象封装了变量 xi,并在调用时执行该操作。

Exprx 是其的一个实例,如下所示

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,一个用于 lvaluelvalue 被赋值为 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++ 提供强大的功能……

© . All rights reserved.