45 votes

Macro / modèle C / C ++ blackmagic pour générer un nom unique

Les Macros sont très bien. Les modèles sont beaux. Assez bien ce que ça fonctionne bien.

L'exemple est OpenGL; mais la technique est le C++ spécifiques et s'appuie sur aucune connaissance de l'OpenGL.

Problème bien précis:

J'ai envie d'une expression E; où je n'ai pas à indiquer un nom unique, à tel point qu'un constructeur est appelé où E est défini, et un destructeur est appelé lorsque le bloc E est dans les extrémités.

Par exemple, pensez à:

class GlTranslate {
  GLTranslate(float x, float y, float z); {
    glPushMatrix();
    glTranslatef(x, y, z);
  }
  ~GlTranslate() { glPopMatrix(); }

};

Solution manuelle:

{
  GlTranslate foo(1.0, 0.0, 0.0); // I had to give it a name
  .....
} // auto popmatrix

Maintenant, j'ai ce non seulement pour glTranslate, mais beaucoup d'autres PushAttrib/PopAttrib appels trop. Je préférerais ne pas avoir à venir avec un nom unique pour chaque var. Est-il une astuce impliquant des macros modèles ... ou quelque chose d'autre qui va automatiquement créer une variable qui est constructeur est appelé au moment de la définition; et destructeur appelé à la fin du bloc?

Merci!

68voto

Je ne voudrais pas le faire personnellement, mais juste de venir avec des noms uniques. Mais si vous voulez le faire, une façon est d'utiliser une combinaison de if et for:

#define FOR_BLOCK(DECL) if(bool _c_ = false) ; else for(DECL;!_c_;_c_=true)

Vous pouvez l'utiliser comme

FOR_BLOCK(GlTranslate t(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate t(1.0, 1.0, 0.0)) {
    ...
  }
}

Chacun de ces noms sont dans des domaines distincts et ne pas entrer en conflit. L'intérieur des noms de masquer l'extérieur noms. Les expressions dans l' if et for boucles sont constants et doit être facilement optimisé par le compilateur.


Si vous avez vraiment envie de passer à une expression, vous pouvez utiliser le ScopedGuard truc (voir Plus Important const), mais il aura besoin d'un peu plus de travail pour l'écrire. Mais le côté sympa, c'est, que nous pouvons nous débarrasser de l' for boucle, et laissez notre objet d'évaluer à l' false:

struct sbase { 
  operator bool() const { return false; } 
};

template<typename T>
struct scont : sbase { 
  scont(T const& t):t(t), dismiss() { 
    t.enter();
  }
  scont(scont const&o):t(o.t), dismiss() {
    o.dismiss = true;
  }
  ~scont() { if(!dismiss) t.leave(); }

  T t; 
  mutable bool dismiss;
};

template<typename T>
scont<T> make_scont(T const&t) { return scont<T>(t); }

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont(E)) ; else

Ensuite, vous fournissez le bon enter et leave fonctions de:

struct GlTranslate {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    glPushMatrix();
    glTranslatef(x, y, z);
  }

  void leave() const {
    glPopMatrix();
  }

  float x, y, z;
};

Maintenant, vous pouvez l'écrire entièrement, sans un nom du côté de l'utilisateur:

FOR_BLOCK(GlTranslate(1.0, 0.0, 0.0)) {
  FOR_BLOCK(GlTranslate(1.0, 1.0, 0.0)) {
    ...
  }
}

Si vous souhaitez passer plusieurs expressions à la fois, c'est un peu plus délicat, mais vous pouvez écrire une expression modèle qui agit sur operator, pour recueillir toutes les expressions en scont.

template<typename Derived>
struct scoped_obj { 
  void enter() const { } 
  void leave() const { } 

  Derived const& get_obj() const {
    return static_cast<Derived const&>(*this);
  }
};

template<typename L, typename R> struct collect 
  : scoped_obj< collect<L, R> > {
  L l;
  R r;

  collect(L const& l, R const& r)
    :l(l), r(r) { }
  void enter() const { l.enter(); r.enter(); }
  void leave() const { r.leave(); l.leave(); }
};

template<typename D1, typename D2> 
collect<D1, D2> operator,(scoped_obj<D1> const& l, scoped_obj<D2> const& r) {
  return collect<D1, D2>(l.get_obj(), r.get_obj());
}

#define FOR_BLOCK(E) if(sbase const& _b_ = make_scont((E))) ; else

Vous avez besoin d'hériter de la RAII objet d' scoped_obj<Class> comme le montre le schéma suivant

struct GLTranslate : scoped_obj<GLTranslate> {
  GLTranslate(float x, float y, float z)
    :x(x),y(y),z(z) { }

  void enter() const {
    std::cout << "entering ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  void leave() const {
    std::cout << "leaving ("
              << x << " " << y << " " << z << ")" 
              << std::endl;
  }

  float x, y, z;
};

int main() {
  // if more than one element is passed, wrap them in parentheses
  FOR_BLOCK((GLTranslate(10, 20, 30), GLTranslate(40, 50, 60))) {
    std::cout << "in block..." << std::endl;
  }
}

L'ensemble de ces impliquent pas de fonctions virtuelles, et les fonctions concernées sont transparentes pour le compilateur. En fait, avec ce qui précède GLTranslate modifié pour ajouter un entier à une variable globale, et avant de quitter le soustraire à nouveau, et le ci-dessous définies GLTranslateE, j'ai fait un test:

// we will change this and see how the compiler reacts.
int j = 0;

// only add, don't subtract again
struct GLTranslateE : scoped_obj< GLTranslateE > {
  GLTranslateE(int x):x(x) { }

  void enter() const {
    j += x;
  }

  int x;
};

int main() {
  FOR_BLOCK((GLTranslate(10), GLTranslateE(5))) {
    /* empty */
  }
  return j;
}

En fait, la GCC au niveau d'optimisation en -O2 sorties ceci:

main:
    sub     $29, $29, 8
    ldw     $2, $0, j
    add     $2, $2, 5
    stw     $2, $0, j
.L1:
    add     $29, $29, 8
    jr      $31

Je n'aurais pas cru qu'il optimisé tout à fait bien!

39voto

GManNickG Points 155079

Si votre compilateur prend en charge __COUNTER__ (il le fait probablement), vous pouvez essayer:

 // boiler-plate
#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define MAKE_UNIQUE(x) CONCATENATE(x, __COUNTER__)

// per-transform type
#define GL_TRANSLATE_DETAIL(n, x, y, z) GlTranslate n(x, y, z)
#define GL_TRANSLATE(x, y, z) GL_TRANSLATE_DETAIL(MAKE_UNIQUE(_trans_), x, y, z)
 

Pour

 {
    GL_TRANSLATE(1.0, 0.0, 0.0);

    // becomes something like:
    GlTranslate _trans_1(1.0, 0.0, 0.0);

} // auto popmatrix
 

11voto

Zeks Points 648

Je pense qu'il est maintenant possible de faire quelque chose comme ça:

 struct GlTranslate
{
    operator()(double x,double y,double z, std::function<void()> f)
    {
        glPushMatrix(); glTranslatef(x, y, z);
        f();
        glPopMatrix();
    }
};
 

puis dans le code

 GlTranslate(x, y, z,[&]()
{
// your code goes here
});
 

De toute évidence, C ++ 11 est nécessaire

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X