web-dev-qa-db-fra.com

Puis-je créer des classes anonymes en C ++ et capturer les variables externes comme en Java?

En Java, lorsque j'ai besoin d'une fonction de rappel, je dois implémenter une classe anonyme. Dans la classe anonyme, je peux accéder aux variables externes si elles sont final.

Maintenant, je fais la même chose en C++. Je comprends que C++ lambda fonctionne mieux mais parfois je dois passer dans de nombreuses fonctions où avec des classes anonymes, je n'ai besoin de passer que dans une seule instance.

J'ai essayé l'exemple suivant. Cela fonctionne avec 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;
}

Est-il possible de capturer les variables externes comme celle-ci?

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

MISE À JOUR:

Le deuxième exemple ne se compilera pas. Les erreurs sont là,

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’

MISE À JOUR:

Je viens de réaliser quelques problèmes supplémentaires en faisant cela:

  • Je ne peux pas écrire de constructeur car la classe n'a pas de nom
  • la liste d'initialisation ne permet pas l'héritage.
  • toute modification pour le compiler rend le code illisible.

Je pense que je dois abandonner les cours anonymes.

37
woodings

Il n'y a aucun moyen de capturer automatiquement ces variables, mais vous pouvez utiliser une approche alternative. C'est si vous voulez capturer par référence:

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;
}

Si vous voulez capturer par valeur, changez simplement int& dans int.

Quoi qu'il en soit, vous pouvez envisager d'utiliser un Tuple of lambdas comme un objet "multi-callback" si c'est ce qui vous dérange pour les lambdas individuels. Vous auriez toujours tout emballé dans un seul objet et la capture se ferait gratuitement.

À titre d'exemple:

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

Vous pouvez capturer la variable manuellement (ce qui est similaire à ce qu'une capture lambda fait en arrière-plan):

int main() {
    int y = 100;
    struct { 
        int& y;
        int operator()(int x) { return x + y; }
    } anon = { y };
}

Vous pouvez ensuite l'utiliser comme ceci:

#include <iostream>
...
std::cout << anon(10) << std::endl;

Imprime 110 comme prévu. Malheureusement, vous ne pouvez pas faire hériter le type anonyme d'un autre avec cette méthode car les types constructibles de liste d'initialisation ne peuvent pas hériter d'un autre type. Si l'héritage est crucial, vous devez utiliser la méthode constructeur décrite par Andy Prowl .

8
Jake Woods

Un lambda C++ peut capturer des variables "extérieures". [Edit: quand j'ai lu la question pour la première fois, j'ai en quelque sorte manqué où il a mentionné qu'il était au courant des lambdas. Pour le meilleur ou pour le pire, C++ n'a rien d'autre qui ressemble vraiment à une classe anonyme].

Par exemple:

#include <iostream>

int main(){ 

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

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

... imprime 102 comme sortie.

Notez que bien qu'elle ressemble un peu à une fonction, un lambda C++ aboutit vraiment à la création d'une classe. Je suppose que je devrais ajouter: cette classe n'est pas techniquement anonyme, mais elle a un nom non spécifié qui n'est jamais directement visible.

Edit: Je suis toujours un peu perplexe quant à la justification de ne pas utiliser de lambdas. L'intention est-elle d'utiliser une classe contenant de nombreuses fonctions membres? Si tel est le cas, la façon dont vous prévoyez de spécifier la fonction membre à invoquer à quel moment/dans quel but n'est pas claire. Ma réaction immédiate est que cela sonne de manière suspecte comme si vous essayez de tordre le langage pour prendre en charge une conception problématique.

5
Jerry Coffin

Ce n'est pas l'anonymat de la classe qui restreint l'accès aux variables externes. Dans la question, y n'est pas accessible car la classe a été définie localement dans une fonction.

Il existe quelques restrictions pour les classes définies localement. Tout d'abord, ils ne peuvent accéder qu'aux variables locales qui sont statiques, mais peuvent accéder à toute autre variable disponible pour la portée de la fonction. En outre, les classes locales ne peuvent pas avoir de membres de données statiques.

Quant aux classes anonymes, vous ne pouvez pas avoir de constructeurs ni de destructeurs. Toutes les fonctions membres doivent être déclarées dans la définition de classe. Il ne peut pas avoir de membres statiques statiques, ce qui inclut les membres constants intégraux statiques qui peuvent normalement être instanciés à l'intérieur d'une définition de classe. L'héritage n'est pas non plus autorisé.

Les classes anonymes sont un coin obscur de C++ et ont peu de valeur pratique. Les fonctions lambda et d'autres techniques sont beaucoup plus flexibles. Mais qui sait, peut-être que dans certaines situations, cela pourrait aider à la lisibilité du code.

2
Jay Hudson

Si votre classe IA n'a vraiment qu'une seule méthode virtuelle que vous devez remplacer (et que la complexité réelle est d'autres méthodes non virtuelles) mais que vous ne voulez pas capturer les variables locales que cette méthode besoins, que diriez-vous de ceci:

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