c++ c-preprocessor raii

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



c-preprocessor raii (3)

Creo que ahora es posible hacer algo como esto:

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

luego en el codigo

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

Obviamente, se necesita C ++ 11.

Las macros están bien. Las plantillas están bien. Casi 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; de tal manera que se llama a un constructor donde se define E, y se llama a un destructor donde el bloque E está en los extremos.

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 muchas otras llamadas PushAttrib / PopAttrib. Preferiría no tener que inventar un nombre único para cada var. ¿Hay algún truco relacionado con las plantillas de macros ... o algo más que creará automáticamente una variable a la que se llama al constructor en el punto de definición; y destructor llamado al final del bloque?

¡Gracias!


Si su compilador soporta __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, sino que solo obtendría nombres únicos. Pero si quieres hacerlo, una forma es usar una combinación de if y 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án en conflicto. Los nombres internos ocultan los nombres externos. Las expresiones en los bucles if y for son constantes y el compilador debe optimizarlas fácilmente.

Si realmente desea pasar una expresión, puede usar el truco de ScopedGuard (vea Const más importante ), pero necesitará algo más de trabajo para escribirla. 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<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

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 recopilar todas las expresiones en un 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

scoped_obj<Class> heredar el objeto RAII de scoped_obj<Class> como se muestra a continuación

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

Todos estos no implican funciones virtuales, y las funciones involucradas son transparentes para el compilador. De hecho, con el GLTranslate anterior cambiado para agregar un entero entero a una variable global y al dejar de restarlo nuevamente, y el GLTranslateE definido a 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 produce 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 habría esperado eso, optimizado bastante bien!