web-dev-qa-db-ja.com

C ++で匿名クラスを作成し、Javaなどの外部変数をキャプチャできますか?

Javaでは、コールバック関数が必要な場合、匿名クラスを実装する必要があります。無名クラス内では、finalである外部変数にアクセスできます。

今、私はC++でも同じことをしています。私はC++ラムダがよりよく機能することを理解していますが、匿名クラスでは1つのインスタンスのみを渡す必要がある多くの関数を渡す必要がある場合があります。

私は次の例を試しました。 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;
}

更新:

2番目の例はコンパイルされません。エラーはこちら、

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’

更新:

これを行う際に、さらにいくつかの問題に気付きました。

  • クラスに名前がないため、コンストラクタを記述できません
  • 初期化子リストは継承を許可しません。
  • コンパイルを変更すると、コードが判読できなくなります。

私は匿名クラスから離れなければならないと思います。

37
woodings

これらの変数を自動的にキャプチャする方法はありませんが、別のアプローチを使用できます。これは、参照によってキャプチャする場合です。

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& into int

とにかく、それが個々のラムダについてあなたを悩ませているのであれば、「マルチコールバック」オブジェクトとしてTuple of lambdasを使用することを検討するかもしれません。それでもすべてを1つのオブジェクトに詰め込んで、キャプチャは無料で行われます。

例として:

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...
    );
37
Andy Prowl

変数を手動でキャプチャできます(これは、ラムダキャプチャが舞台裏で行うことと似ています)。

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が概説したコンストラクターメソッド を使用する必要があります。

8
Jake Woods

C++ラムダは、「外部」変数をキャプチャできます。 [編集:質問を最初に読んだとき、ラムダに気付いていると彼が言ったところをどこかで見逃しました。良くも悪くも、C++には匿名クラスに本当に似たものは他にありません]。

例えば:

#include <iostream>

int main(){ 

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

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

... prints 102その出力として。

関数のように見えますが、C++ラムダは実際にクラスを作成することに注意してください。追加する必要があると思います。そのクラスは技術的に匿名ではありませんが、直接表示されることのない不特定の名前を持っています。

編集:私はまだラムダを使用しないことの正当化について少し困惑しています。多くのメンバー関数を含む1つのクラスを使用するつもりですか?もしそうなら、どのメンバー関数をどの時間に/どの目的で呼び出すかを明確にする方法は明確ではありません。私の即座の反応は、問題のあるデザインをサポートするために言語をひねろうとしているように疑わしく聞こえるということです。

5
Jerry Coffin

外部変数へのアクセスを制限するのは、クラスの匿名性ではありません。質問では、クラスは関数内でローカルに定義されているため、yにはアクセスできません。

ローカルに定義されたクラスにはいくつかの制限があります。まず、静的なローカル変数にのみアクセスできますが、関数のスコープで使用可能な他の変数にはアクセスできます。また、ローカルクラスに静的データメンバーを含めることはできません。

匿名クラスについては、コンストラクタもデストラクタも使用できません。すべてのメンバー関数は、クラス定義内で宣言する必要があります。静的な静的メンバーを持つことはできません。これには、通常クラス定義内でインスタンス化できる静的な静的な統合メンバーが含まれます。また、継承は許可されていません。

匿名クラスはC++のわかりにくいコーナーであり、実用的な価値はほとんどありません。 Lambda関数およびその他の手法は、はるかに柔軟です。しかし、おそらく状況によっては、コードが読みやすくなる可能性があります。

2
Jay Hudson

IAクラスにオーバーライドする必要がある仮想メソッドが実際には1つしかなく(実際の複雑さは他の非仮想メソッドである)、ローカル変数をキャプチャしたくない場合このメソッドneeds、これはどうですか:

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
leftaroundabout