2010-03-10 14 views
37

Las macros están bien. Las plantillas están bien. Prácticamente todo lo que funciona está bien.C/C++ macro/template blackmagic para generar un nombre único

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 unas pocas cosas, no sólo para glTranslate, pero mucha otra PushAttrib/PopAttrib llamadas también. 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!

+3

no veo por qué pensar en un nombre único es más difícil que la realización de algún conjunto de la llamada macro. –

+5

Por lo que vale, intenté un esquema similar alguna vez. Descubrí que era más fácil crear alguna clase de 'Transformation' que tuviera' push/pop' como tú, con funciones de miembros que hacen llamadas para traducir, etc. Entonces solo tienes una clase, y tú solo estás empujando cuando lo necesites. – GManNickG

+2

Creo que la respuesta es __LINE__ o __COUNTER__ :-) – anon

Respuesta

35

Si su compilador soporta __COUNTER__ (probablemente lo hace), usted 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) 

Para

{ 
    GL_TRANSLATE(1.0, 0.0, 0.0); 

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

} // auto popmatrix 
+1

Debe evitar el inicio de identificadores con el carácter de subrayado. Los nombres que comienzan con guión bajo están reservados para el compilador, y es posible que tengas una colisión de nombre difícil de rastrear si los generas tú mismo. (Entonces, por ejemplo, reemplace "_trans_" con "trans_" o algo más único) –

+13

@Magnus, GMan está bien usando '_trans'. Estos nombres solo están reservados en el espacio de nombres global o en el espacio de nombres estándar. Los nombres que están reservados en todas partes son los que se parecen a '_Trans' o' __trans'. –

+10

'__LINE__ se puede usar igual que' __COUNTER__ – Corwin

57

No haría esto personalmente, pero solo se me ocurren nombres únicos. Pero si quieres hacerlo, una forma es utilizar una combinación de if y for:

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

Usted puede 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 se encuentran en ámbitos separados y ganaron' t 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 utilizar el truco ScopedGuard (ver Most Important const), pero necesitará algo más de trabajo para escribirlo. Pero el lado bueno es que podemos deshacernos del bucle for, y dejar que nuestro objeto evaluar a 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 adecuadas enter y leave funciones:

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 escribir por completo 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 en operator, para recopilar todas las expresiones 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 

Necesitas heredar el objeto de RAII scoped_obj<Class> como lo muestra el siguiente

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

Todas ellas implican ninguna de las funciones virtuales, y las funciones involucradas son transparentes para el compilador. De hecho, con lo anterior GLTranslate cambiar para añadir un único entero a una variable global y al salir de restarlo de nuevo, y el siguiente define 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, el CCG a nivel de optimización -O2 salidas 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 me esperaba eso, lo optimicé bastante bien!

+0

¿Hacer 'bool _c_ = false' deshacerse de las advertencias del compilador o algo así? (Sobre solo 'falso') EDIT: Derp, never-mind, veo por qué. Qué truco tan inteligente. :] – GManNickG

+7

@GMan He tomado este truco de la macro 'BOOST_FOREACH' :) –

+1

+1 trucos interesantes (y afortunadamente útiles). – Tronic

8

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 código

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

Obviamente, C++ 11 se necesita

+4

Me gusta, probablemente puedas cambiar esa 'std :: function' a una plantilla para asegurar que la alineación sea posible :-) –

Cuestiones relacionadas