我能否在C++中创建匿名类并像Java中那样捕获外部变量?

43

在Java中,当我需要一个回调函数时,我必须实现一个匿名类。在匿名类内部,如果它们是final类型的话,我可以访问外部变量。

现在我正在用C++做同样的事情。我知道C++ Lambda表达式比较好用,但有时我需要传入多个函数,而使用匿名类时,我只需要传入一个实例。

我尝试了下面的代码示例。它可以在GCC 4.3.4中工作。

class IA {
public:
  virtual int f(int x) = 0;  
};

int main() {
    class : public IA {
        int f(int x) { return x + 1; }
    } a;
    doFancyWork(&a);
    return 0;
}

能够像这样捕获外部变量吗?

int main() {
    int y = 100; // mark y as final if possible
    class : public IA {
        int f(int x) { return x + y; }
    } a;
    return 0;
}

更新:

第二个示例无法编译。错误在这里:

prog.cpp: In member function ‘virtual int main()::<anonymous class>::f(int)’:
prog.cpp:9: error: use of ‘auto’ variable from containing function
prog.cpp:7: error:   ‘int y’ declared here
prog.cpp: In function ‘int main()’:
prog.cpp:7: warning: unused variable ‘y’

更新:

我刚刚意识到此操作有几个问题:

  • 我无法编写构造函数,因为类没有名称
  • 初始化列表不允许继承。
  • 任何改变都会使代码难以阅读。

我认为我需要放弃使用匿名类。


第一个例子编译通过了(在http://ideone.com/上测试过)。第二个没有。 - woodings
2
要捕获y的值,您需要向匿名类添加成员y。(或者lambda会自动以这种方式处理“捕获”) - aschepler
编译器错误是什么? - Code-Apprentice
1
你可以在提供对象作为回调之前,向 IA 添加一个成员变量 _y,并将其设置为 y 的值。 - Andy Prowl
@PlasmaHH 我认为他在Ideone上使用的是C++(而不是C++0x)编译器,它是GCC 4.3.4。 - JKor
显示剩余2条评论
5个回答

42

没有办法自动地捕获这些变量,但您可以使用另一种方法。如果您想通过引用捕获:

int main() {
    int y = 100; // mark y as final if possible
    class IB : public IA {
    public:
      IB(int& y) : _y(y) {}
      int f(int x) { return x + _y; }
    private:
      int& _y;
    } a (y);
    return 0;
}
如果您想按值捕获,只需将int&更改为int。 无论如何,如果您对单个lambda表达式感到不满意,可以考虑使用lambda元组作为“多回调”对象。您仍然可以将所有内容打包在一个对象中,而且捕获将是免费的。 这只是一个例子:
auto callbacks = make_tuple(
    [] (int x) { cout << x << endl; },
    [&] () { cout << y << endl; }, // y is captured by reference
    [=] (int x) { cout << x + y << endl; }, // y is captured by value
    // other lambdas here, if you want...
    );

4
对于元组中包含lambda表达式的做法点赞。这是一个非常优雅的解决手段。 - Jake Woods
1
@JakeWoods:谢谢。我建议使用它,因为您不必手动捕获变量。 - Andy Prowl
嗨,我有一个问题,我使用lambda创建了元组并将其添加到Map中,但是当我迭代Map并从元组中获取第一个lambda并调用函数时,程序崩溃了。这里的Map是字段变量,在一个函数中放置元组并在另一个函数中获取它们。谢谢! - Bahramdun Adil

9
您可以手动捕获变量(类似于lambda捕获在幕后执行的操作):
int main() {
    int y = 100;
    struct { 
        int& y;
        int operator()(int x) { return x + y; }
    } anon = { y };
}

您可以像这样使用它:
#include <iostream>
...
std::cout << anon(10) << std::endl;

按预期打印110。不幸的是,使用此方法无法使匿名类型继承另一个类型,因为初始化列表可构造类型无法继承另一种类型。如果继承至关重要,则应使用Andy Prowl提出的构造函数方法


很抱歉,这段代码无法编译...需要做一些更改。 - Andy Prowl
这段代码在我的公司使用的编译器gcc 4.7.1上无法编译通过。错误信息如下:error: could not convert ‘{1}’ from ‘<brace-enclosed initializer list>’ to ‘main()::<anonymous class>’。 - woodings
如果继承非常重要,那么确实需要避免使用lambda表达式。如果不是这样,而只是想要一个匿名函数对象,那么确实没有必要避免使用lambda表达式。 - Christian Rau

5

C++ lambda表达式可以捕获“外部”变量。[编辑:当我第一次阅读问题时,我不知怎么错过了他提到他知道lambda表达式的地方。无论好坏,C++没有其他类似于匿名类的东西]。

例如:

#include <iostream>

int main(){ 

    int y = 100;
    auto lambda = [=](int x) { return x + y; };

    std::cout << lambda(2);
}

...以102作为输出。

请注意,尽管它看起来有点像函数,但C++的lambda实际上是创建一个类的结果。我想我应该补充说:那个类不是匿名的,但它有一些未指定的名称,从未直接可见。

编辑:我还有点困惑于不使用lambda的理由。是否意图使用包含多个成员函数的一个类?如果是这样,那么你计划如何指定在哪个时间/为哪个目的调用哪个成员函数呢?我的第一反应是,这听起来很可疑,好像你正在试图扭曲语言以支持一个有问题的设计。


2
他似乎非常清楚这一点,但不想使用lambda,因为他希望它能够同时封装许多不同的函数。虽然我从内心深处不赞成他的方法,并且也建议只使用lambda,但你的答案仍然没有真正回答实际问题。 - Christian Rau
@ChristianRau:糟糕,我错过了他提到了了解lambda的地方。 - Jerry Coffin
1
@JerryCoffin 我需要将事件处理程序传递给其他人,我可以在其中实现多个函数,例如onEvent()、onErr()和destroy()。因为可能有很多事件处理程序,我不想将它们定义为单独的类并为它们分配名称。使用lambda函数,似乎我必须逐个传递它们或以任何方式将它们放入容器类中。 - woodings
关于您的编辑,听起来他的 doFancyWork 函数知道要调用哪些函数。因此,它不仅使用某些模板化函数类型的 () 运算符,而是期望一个抽象接口,其中包含他的匿名类实现的一堆方法。最终采用了通常的 Java 风格回调。尽管如此,仍然有疑问是否应该将这种风格真正转移到另一种语言中,该语言具有自己的成语(同样灵活)的回调方式。 - Christian Rau

1
如果你的IA类只有一个需要重写的虚方法(而真正的复杂性在于其他非虚方法),但你不想捕获这个方法所需的局部变量,那么可以考虑以下方法:
int main() {
  int y = 100;
  auto f = [=](int x){return x+y;};
  typedef decltype(f) F;
  struct IB : IA {
    F _f;
    IB(F _f): _f(_f) {}
    int f(int x) { return _f(x); }
  } a(f);
  doFancyWork(&a);
  return 0;
}

1

类的匿名性并不会限制访问外部变量。在这个问题中,y无法被访问是因为该类被局部定义在一个函数内。

对于本地定义的类,有几个限制。首先,它们只能访问静态局部变量,但可以访问函数作用域下任何其他可用变量。此外,本地类无法具有静态数据成员。

至于匿名类,您不能拥有构造函数或析构函数。所有成员函数必须在类定义内声明。它不能拥有静态静态成员,包括通常可以在类定义内实例化的const静态整型成员。另外,不允许继承。

匿名类是C++的一个晦涩角落,实际价值很小。Lambda函数和其他技术更加灵活。但谁知道,在某些情况下,它可能有助于提高代码的可读性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接