2009-06-05 31 views
10

En mi búsqueda épica de hacer C++ hacer cosas que no debería, estoy tratando de armar una clase generada en tiempo de compilación.C++ Code Generation

Sobre la base de una definición de preprocesador, tales como (concepto bruto)

CLASS_BEGIN(Name) 
    RECORD(xyz) 
    RECORD(abc) 

    RECORD_GROUP(GroupName) 
     RECORD_GROUP_RECORD(foo) 
     RECORD_GROUP_RECORD(bar) 
    END_RECORDGROUP 
END_CLASS 

Aunque estoy bastante seguro de que genero una clase que lee los datos del sistema de archivos que utiliza este tipo de estructura (tal vez incluso hacerlo utilizando la metaprogramación de plantillas), no veo cómo puedo generar tanto las funciones para acceder a los datos como la función para leer los datos.

me gustaría terminar con una clase algo como esto

class Name{ 
    public: 
    xyz_type getxyz(); 
    void setxyz(xyz_type v); 

    //etc 

    list<group_type> getGroupName(); 

    //etc 

    void readData(filesystem){ 
     //read xyz 
     //read abc 
     //etc 
    } 
}; 

¿Alguien tiene alguna idea de si esto es posible?

--EDIT--

Para aclarar el uso previsto para ello. Tengo archivos en un formato estándar que quiero leer. El formato ya está definido, por lo que no está abierto a cambios. Cada archivo puede contener cualquier número de registros, cada uno de los cuales puede contener cualquier número de registros secundarios.

Los numerosos tipos de registros contienen un conjunto diferente de registros secundarios, pero se pueden definir. Entonces, por ejemplo, el registro de mapa de altura debe contener un mapa de altura, pero puede contener opcionalmente normales.

Así que me gustaría definir un registro para que de este modo:

CLASS_BEGIN(Heightmap) 
    RECORD(VHDT, Heightmap, std::string) //Subrecord Name, Readable Name, Type 
    RECORD_OPTIONAL(VNML, Normals, std::string) 
END_CLASS 

Por lo que yo quiero mostrar algo, con la funcionalidad de una clase como esta:

class Heightmap{ 
    public: 
    std::string getHeightmap(){ 
     return mHeightmap->get<std::string>(); 
    } 
    void setHeightmap(std::string v){ 
     mHeight->set<std::string>(v); 
    } 

    bool hasNormal(){ 
     return mNormal != 0; 
    } 
    //getter and setter functions for normals go here 

    private: 
    void read(Record* r){ 
     mHeightmap = r->getFirst(VHDT); 
     mNormal = r->getFirst(VNML); 
    } 


    SubRecord* mHeightmap, mNormal; 
} 

La cuestión Lo que estoy teniendo es que necesito cada definición de preprocesador dos veces. Una vez para definir la definición de función dentro de la clase, y una vez para crear la función de lectura. Como el preprocesador es puramente funcional, no puedo enviar los datos a una cola y generar la clase en la definición de marco END_CLASS.

No veo una forma de solucionar este problema, pero me pregunto si alguien que tenga una mayor comprensión de C++ lo hizo.

+0

Es posible, sospecho. ¿Puedes ser más preciso sobre exactamente dónde estás golpeando la pared? –

+0

No es obvio qué ventajas obtienes al hacer esto, por favor explica. –

Respuesta

5

Es posible que pueda resolver este problema utilizando boost tuples. Te dará como resultado un diseño que es diferente de lo que estás pensando ahora, pero debería permitirte resolver el problema de una manera genérica.

El siguiente ejemplo define un registro de la forma "std :: string, bool" y luego lee esos datos desde una secuencia.

#include "boost/tuple/tuple.hpp" 
#include <iostream> 
#include <sstream> 

using namespace ::boost::tuples; 

Las funciones se utilizan para leer los datos de un istream. La primera de sobrecarga detiene la iteración a través de la tupla después de llegar a la última tipo de registro:

// 
// This is needed to stop when we have no more fields 
void read_tuple (std::istream & is, boost::tuples::null_type) 
{ 
} 

template <typename TupleType> 
void read_tuple (std::istream & is, TupleType & tuple) 
{ 
    is >> tuple.template get_head(); 
    read_tuple (is, tuple.template get_tail()); 
} 

la siguiente clase implementa el miembro captador para nuestro registro. Utilizando el RecordKind como la llave de la que obtenemos el miembro específico que nos interesa.

template <typename TupleType> 
class Record 
{ 
private: 
    TupleType m_tuple; 

public: 
    // 
    // For a given member - get the value 
    template <unsigned int MBR> 
    typename element <MBR, TupleType>::type & getMember() 
    { 
    return m_tuple.template get<MBR>(); 
    } 

    friend std::istream & operator>> (std::istream & is 
            , Record<TupleType> & record) 
    { 
    read_tuple (is, record.m_tuple); 
    } 
}; 

El siguiente tipo es la descripción de la meta de nuestro registro. La enumeración nos da un nombre simbólico que podemos usar para acceder a los miembros, es decir. los nombres de los campos. La tupla a continuación se definen los tipos de esos campos:

struct HeightMap 
{ 
    enum RecordKind 
    { 
    VHDT 
    , VNML 
    }; 

    typedef boost::tuple < std::string 
         , bool 
        > TupleType; 
}; 

Por último, se construye un registro y leer en algunos datos de una corriente:

int main() 
{ 
    Record<HeightMap::TupleType> heightMap; 
    std::istringstream iss ("Hello 1"); 

    iss >> heightMap; 

    std::string s = heightMap.getMember <HeightMap::VHDT>(); 
    std::cout << "Value of s: " << s << std::endl; 


    bool b = heightMap.getMember <HeightMap::VNML>(); 
    std::cout << "Value of b: " << b << std::endl; 
}  

Y como esto es todo código de la plantilla, debe ser capaz de tener registros anidados en registros.

8

Si está buscando una forma de serializar/deserializar los datos con la generación de código C++, me fijaría en Google protobufs (http://code.google.com/p/protobuf/) o en el Thrift de Facebook (http://incubator.apache.org/thrift/).

Para protobufs, se escribe una definición de datos, así: entonces se genera

clase
message Person { 
    required string name = 1; 
    required int32 id = 2; 
    optional string email = 3; 

    enum PhoneType { 
    MOBILE = 0; 
    HOME = 1; 
    WORK = 2; 
    } 

    message PhoneNumber { 
    required string number = 1; 
    optional PhoneType type = 2 [default = HOME]; 
    } 

    repeated PhoneNumber phone = 4; 
} 

Una persona C++ que permite cargar, guardar y acceder a estos datos. También se pueden generar Python, Java, etc.

2

podría jugar con un mixin registro para hacer algo similar - agregar funcionalidad a una clase automagicamente en tiempo de compilación

template<class Base, class XyzRecType> 
    class CRecord : public Base 
    { 
    protected: 
     RecType xyz; 
    public: 
     CRecord() : Base() {} 


     RecType Get() {return xyz;} 

     void Set(const RecType& anXyz) {xyz = anXyz;} 

     void ReadFromStream(std::istream& input) 
     { 
      ... 
     } 

    }; 

    class CMyClass 
    { 
    }; 

    int main() 
    { 
     // now thanks to the magic of inheritance, my class has added methods! 
     CRecord<CMyClass, std::string> myClassWithAStringRecord; 

     myClassWithAStringRecord.Set("Hello"); 

    } 
0

No estoy exactamente seguro lo que estás buscando en algunos casos.

  • ¿Qué le sucede a foo y bar en la especificación?
  • ¿Qué devuelve getGroupName realmente? (foo, bar)? o GroupName?

Parece que está tratando de crear un mecanismo para la carga y el acceso a estructuras de disco de la disposición arbitraria. Es esto exacto? (Editar: acaba de notar la función de miembro "establecer" ... así que supongo que está buscando la serialización completa)

Si está en un sistema * nix, especificando su propio compilador para compilarlo a .o (probable un script perl/python/what-have-you que termina con una llamada a gcc) en el Makefile es una solución trivial. Otros pueden saber cómo hacerlo en Windows.

3

Esta es una técnica que uso mucho en C y C++, llamada "macro de lista". Supongamos que tiene una lista de cosas como variables, mensajes de error, códigos de operación del intérprete o cualquier cosa sobre qué código repetitivo debe escribirse. En tu caso, son variables de miembros de clase.

Supongamos que son variables. Ponerlos en una macro lista como esta:

#define MYVARS \ 
DEFVAR(int, a, 6) \ 
DEFVAR(double, b, 37.3) \ 
DEFARR(char, cc, 512) \ 

Declarar las variables, hacer esto:

#define DEFVAR(typ,nam,inival) typ nam = inival; 
#define DEFARR(typ,nam,len) typ nam[len]; 
    MYVARS 
#undef DEFVAR 
#undef DEFARR 

Ahora puede generar cualquier tipo de código repetitivo simplemente mediante la redefinición de defvar y DEFARR, y crear instancias de MYVARS .

Algunas personas encuentran esto bastante discordante, pero creo que es una manera perfectamente buena de usar el preprocesador como un generador de código, y lograr DRY. Y, la macro de la lista se convierte en una mini-DSL.

+0

En C, sí. A menudo, en C++ buscaré primero una solución TMP. Esto podría explicar de alguna manera por qué soy más productivo en C. –

+0

Mi preferencia entre C y C++ depende de lo que estoy haciendo, pero lo entiendo por completo. –

1

En general, puede lograr exactamente lo que desea si fusiona todo en una macro y luego aprovecha la biblioteca del preprocesador Booost para definir su clase.Mira cómo implementé la macro MACE_REFLECT que hace una especialización parcial de una clase completa y debe hacer referencia a cada nombre dos veces en diferentes partes.

Esto es muy similar a cómo analizo automáticamente JSON en estructuras con la ayuda del preprocesador.

dado tu ejemplo, yo traduciría como tal:

struct Name { 
    xyz_type xyz; 
    abc_type abc; 
    boost::optional<foo_type> foo; 
    boost::optional<bar_type> bar; 
}; 
MACE_REFLECT(Name, (xyz)(abc)(foo)(bar)) 

que ahora puede 'visitar' los miembros de Nombre de mi programa de análisis:

struct visitor { 
    template<typename T, T p> 
    inline void operator()(const char* name)const { 
     std::cout << name << " = " << c.*p; 
    } 
    Name c; 
}; 
mace::reflect::reflector<Name>::visit(visitor()); 

Si los objetos se puede representar como estructuras, matrices, pares clave-valor y primitivas, esta técnica funciona de maravilla y me da serialización/deserialización instantánea a/desde json/xml o su formato de registro personalizado.

https://github.com/bytemaster/mace/blob/master/libs/rpc/examples/jsonv.cpp

+0

Upvoted por su genialidad infame, pero, AFAICU (HYGWIMBTA = "Espero que obtienes lo que quiero decir por ese acrónimo"), no quería escribir nombres de campo dos veces. Tu concat. macro solo requiere eso. –