2009-06-21 8 views
3

Je développe un programme où je me trouve faire cela apprecier:blocs Ruby, les fermetures de Java en C++

void Model::SetCollideMode(const std::string &m) 
{ 
    Body *body; 

    std::map<std::string, Body* >::iterator iter; 

    for (iter=this->bodies.begin(); iter!=this->bodies.end(); iter++) 
    { 
    body = iter->second; 

    body->SetCollideMode(m); 
    } 
} 

J'ai plusieurs méthodes de ce genre dans plusieurs objet qui appliquent essentiellement une propriété à tous ses enfants. En venant de monde Ruby Je suis en train de mourir à faire quelque chose comme:

for_all_bodies{ body->SetCollideMode(m) } 

Il y a de toute façon de rendre ce code plus ou fermetures comme dans d'autres d'améliorer comme il? Je suis conscient de comment C++ fonctionne, qu'il est basé sur la pile et il n'y a aucune information de contexte pour créer une fermeture parfaite comme la fonctionnalité (ceci a besoin d'une VM?) Mais au moins s'améliore au cours de répéter ce code 100 fois gentil de programmation.

Répondre

4

Il existe plusieurs approches, aucune d'entre elles n'est parfaite.

D'abord, l'approche « classique » serait de définir un foncteur à faire ce que vous avez besoin:

struct SetCollideModeFunc { 
    SetCollideModeFunc(const std::string& m) : m(m) {} 
    void operator()(std::pair<std::string, Body*>& p) { 
    Body* b = p.second; 
    b->SetCollideMode(m); 
    } 

    const std::string& m; 
}; 

void Model::SetCollideMode(const std::string &m) 
{ 
    std::for_each(bodies.begin(), bodies.end(), SetCollideModeFunc(m)); 
} 

Cela ne vous sauve pas beaucoup de code, mais il ne vous permet de séparer l'itération de l'opération que vous voulez appliquer. Et si vous devez définir plusieurs fois le mode de collage, vous pouvez réutiliser le foncteur, bien sûr.

Une version plus courte est possible avec la bibliothèque Boost.Lambda, ce qui vous permettrait de définir le foncteur en ligne. Je ne me souviens pas la syntaxe exacte, comme je ne l'utilise pas Boost.Lambda souvent, mais ce serait quelque chose comme ceci:

std::for_each(bodies.begin(), bodies.end(), _1.second->SetCollideMode(m)); 

Dans C++ 0x, vous obtenez un soutien linguistique pour les lambdas, ce qui permet syntaxe similaire à cela sans avoir à tirer dans les bibliothèques tierces.

Enfin, Boost.ForEach pourrait être une option, ce qui permet une syntaxe telle que:

void Model::SetCollideMode(const std::string &m) 
{ 
    BOOST_FOREACH ((std::pair<std::string, Body*> p), bodies) // note the extra parentheses. BOOST_FOREACH is a macro, which means the compiler would choke on the comma in the pair if we do not wrap it in an extra() 
    { 
    p.second->SetCollideMode(m); 
    } 
} 
+2

BOOST_FOREACH est une macro. Cette virgule dans le type de paire rendra le compilateur fou. – Ferruccio

+0

Bon point. Fixe :) – jalf

+0

Je pense que boost_foreach est la plus utilisable des solutions ... – Jordi

5

En C++ 0x, oui. See here. Comme vous l'avez deviné, elles sont faites de la manière C++ caractéristique, c'est-à-dire si vous fermez accidentellement une variable de pile et laissez l'objet lambda survivre plus longtemps que la pile, vous avez un comportement indéfini. C'est une toute nouvelle façon de faire planter votre programme! Mais c'est injuste - à bien des égards, ils sont plus sophistiqués que les lambdas dans de nombreuses autres langues, parce que vous pouvez déclarer la mesure dans laquelle ils sont autorisés à muter l'état.

Jusque-là, there have been attempts to emulate the same thing, mais ils sont probablement plus de problèmes qu'ils ne le méritent.

+0

"C'est une toute nouvelle façon de faire planter votre programme!" Je considère que c'est une innovation passionnante d'une manière éprouvée de faire planter votre programme, en créant une référence dangling à une variable de pile. Vous "juste" devez vous rappeler que les fermetures, ainsi que les déclarations de retour et les fonctions params de type pointeur ou référence, ont le potentiel de laisser les références échapper à leur durée de stockage. –

2

Vous pouvez utiliser le Boost.Foreach:

#include <boost/foreach.hpp> 

void Model::SetCollideMode(const std::string &m) 
{ 
    typedef pair<std::string, Body*> body_t; 
    BOOST_FOREACH (body_t& body, bodies) 
    { 
    body.second->SetCollideMode(m); 
    } 
} 
+1

Je pense que ce serait une boucle sur la paire

+0

C'est vrai, j'ai oublié que c'était une carte. – Ferruccio

+0

vous devez également avoir typedef paire <** const ** std :: string, Body *> body_t; Si vous voulez utiliser la syntaxe de référence (body_t & body) –

4

BOOST_FOREACH (ou la nouvelle boucle à base de gamme) est probablement la voie à suivre, mais voici comment je l'approche normalement lambda dans la norme actuelle, en utilisant bind TR1:

#include <algorithm> 
#include <functional> 
using namespace std; 

void Model::SetCollideMode(const std::string &m) 
{ 
    for_each(bodies.begin(),bodies.end(), 
      tr1::bind(&Body::SetCollideMode, 
        tr1::bind(&pair<std::string, Body*>::second, _1), m)); 
} 
+0

oof, la syntaxe bind me fait toujours mal à la tête. Je me contente généralement d'écrire des foncteurs séparés à la place. Aucune des deux solutions n'est jolie. Dans l'attente de la prise en charge intégrée de 0x pour lambdas. :) – jalf

+0

Ceci est vraiment méchant .. Je ne peux pas attendre C++ 0x lamda! – StackedCrooked

+0

C'est certainement un goût acquis :), mais après s'être habitué au style, je l'aime mieux que les foncteurs personnalisés pour la plupart des situations (Il devient encore plus salissant avec des fonctions surchargées ou conditionnelles) –

1

C++ ne supporte pas encore lambda de. J'utilise parfois cette solution de contournement:

#include <boost/bind.hpp> 
void Model::SetCollideMode(const std::string &m) 
{  
    typedef std::map<std::string, Body* > Bodies; 
    struct Helper 
    { 
     static SetCollideMode(const std::pair<std::string, Body*> & value, 
          const std::string & m) 
     { 
      value.second->SetCollideMode(m); 
     } 
    }; 

    for_each(bodies.begin(), 
      bodies.end(), 
      boost::bind(Helper::SetCollideMode,_1, m)); 
} 

Juste mes 2 cents ..

+0

Etes-vous sûr de l'avoir déjà fait pour de vrai? Vous ne pouvez pas faire référence à une variable locale de la fonction englobante dans une fonction membre statique d'un type local (dans ce cas votre pointeur "body"). Pourquoi ne pas simplement avoir: value-> second-> SetCollideMode (m); –

+0

Oups, oui, en effet cela ne fonctionnera pas. En effet, value-> second-> SetCollideMode (m); marchera. J'ai utilisé la variable 'body' parce que je voulais conserver l'exemple original autant que possible (en ne réalisant pas que je ne pouvais pas y accéder dans ce cadre). – StackedCrooked

Questions connexes