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’

更新:

我刚刚意识到这样做还有一些问题:

  • 我无法编写构造函数,因为该类没有名称
  • 初始化列表不允许继承。
  • 使其编译的任何更改都会使代码不可读。

我想我必须远离匿名课程。

4

5 回答 5

38

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

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.

无论如何,如果您对单个 lambdas 感到困扰,您可以考虑使用lambdas 元组作为“多回调”对象。您仍然可以将所有东西都打包在一个对象中,并且可以免费进行捕获。

举个例子:

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...
    );
于 2013-01-16T22:14:12.890 回答
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 概述的构造方法

于 2013-01-16T22:11:53.113 回答
5

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

例如:

#include <iostream>

int main(){ 

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

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

...102作为其输出打印。

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

编辑:我仍然对不使用 lambdas 的理由感到有些困惑。是否打算使用一个包含许多成员函数的类?如果是这样,则不清楚您计划如何指定在哪个时间/出于何种目的调用哪个成员函数。我的直接反应是,这听起来很可疑,就好像您试图扭曲语言以支持有问题的设计一样。

于 2013-01-16T22:01:45.253 回答
2

限制对外部变量访问的不是类的匿名性。在问题中, y 不可访问,因为该类是在函数中本地定义的。

本地定义的类有一些限制。首先,它们只能访问静态的局部变量,但可以访问函数范围内可用的任何其他变量。此外,本地类不能有静态数据成员。

至于匿名类,你不能有构造函数也不能有析构函数。所有成员函数都必须在类定义中声明。它不能有静态静态成员,这包括通常可以在类定义中实例化的 const 静态整数成员。也不允许继承。

匿名类是 C++ 的一个不起眼的角落,几乎没有实用价值。Lambda 函数和其他技术要灵活得多。但谁知道呢,也许在某些情况下它可以帮助提高代码的可读性。

于 2015-12-15T16:12:53.650 回答
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;
}
于 2013-01-16T22:28:12.200 回答