C / C ++ macro / template blackmagic para generar un nombre único

Las macros están bien. Las plantillas están bien. Prácticamente todo lo que funciona está bien.

El ejemplo es OpenGL; pero la técnica es específica de C ++ y no se basa en el conocimiento de OpenGL.

Problema preciso:

Quiero una expresión E; donde no tengo que especificar un nombre único; tal que se llama a un constructor donde se define E, y se llama a un destructor donde termina el bloque E.

Por ejemplo, considere:

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

Solución manual:

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

Ahora, tengo esto no solo para glTranslate, sino también para muchas otras llamadas PushAttrib / PopAttrib. Preferiría no tener que encontrar un nombre único para cada var. ¿Hay algún truco que involucre plantillas de macros … o algo más que creará automáticamente una variable cuyo constructor se llama en el punto de definición; y destructor llamado al final del bloque?

¡Gracias!

Si su comstackdor admite __COUNTER__ (probablemente lo haga), podría intentar:

 // 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) 

por

 { GL_TRANSLATE(1.0, 0.0, 0.0); // becomes something like: GlTranslate _trans_1(1.0, 0.0, 0.0); } // auto popmatrix 

Yo no haría esto personalmente, pero solo inventaría nombres únicos. Pero si quieres hacerlo, una forma es usar una combinación de if for :

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

Puedes usarlo como

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

Cada uno de esos nombres está en ámbitos separados y no entrará en conflicto. Los nombres internos ocultan los nombres externos. Las expresiones en los bucles if y for son constantes y deben ser optimizadas fácilmente por el comstackdor.


Si realmente quiere pasar una expresión, puede usar el truco de ScopedGuard (consulte la configuración más importante ), pero necesitará más trabajo para escribirlo. Pero el lado bueno es que podemos deshacernos del bucle for y dejar que nuestro objeto se evalúe como false .

 struct sbase { operator bool() const { return false; } }; template struct scont : sbase { scont(T const& t):t(t), dismiss() { t.enter(); } scont(scont const&o):t(ot), dismiss() { o.dismiss = true; } ~scont() { if(!dismiss) t.leave(); } T t; mutable bool dismiss; }; template scont make_scont(T const&t) { return scont(t); } #define FOR_BLOCK(E) if(sbase const& _b_ = make_scont(E)) ; else 

A continuación, proporciona las funciones de enter y leave adecuadas:

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

Ahora puede escribirlo completamente sin un nombre en el lado del usuario:

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

Si desea pasar varias expresiones a la vez, es un poco más complicado, pero puede escribir una plantilla de expresión que actúe sobre el operator, para recostackr todas las expresiones en un scont .

 template struct scoped_obj { void enter() const { } void leave() const { } Derived const& get_obj() const { return static_cast(*this); } }; template struct collect : scoped_obj< collect > { 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 collect operator,(scoped_obj const& l, scoped_obj const& r) { return collect(l.get_obj(), r.get_obj()); } #define FOR_BLOCK(E) if(sbase const& _b_ = make_scont((E))) ; else 

scoped_obj heredar el objeto RAII de scoped_obj como en los siguientes espectáculos

 struct GLTranslate : scoped_obj { 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; } } 

Todos estos no implican funciones virtuales, y las funciones involucradas son transparentes para el comstackdor. De hecho, con el GLTranslate anterior cambiado para agregar un entero único a una variable global y al dejar de restarlo nuevamente, y el GLTranslateE , hice una prueba:

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

De hecho, GCC en el nivel de optimización -O2 genera esto:

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

No hubiera esperado eso, ¡optó bastante bien!

Creo que ahora es posible hacer algo como esto:

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

luego en el código

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

Obviamente, se necesita C ++ 11