2010-09-16 8 views
5

He escrito los rudimentos de una clase para crear estructuras dinámicas en C++. Los miembros de la estructura dinámica se almacenan contiguamente (en la medida en que indican mis pruebas) el mismo relleno que el compilador insertaría en la estructura estática equivalente. Por lo tanto, las estructuras dinámicas pueden convertirse implícitamente en estructuras estáticas para la interoperabilidad con las API existentes.¿En qué plataformas se bloqueará y cómo puedo mejorarlo?

En primer lugar, no confío en mí mismo para poder escribir código de calidad Boost que pueda compilar y trabajar en más o menos cualquier plataforma. ¿Qué partes de este código están peligrosamente en necesidad de modificación?

Tengo otra pregunta relacionada con el diseño: ¿es el acceso con plantillas de get la única forma de proporcionar al compilador la información de tipo estático necesaria para el código de tipo seguro? Tal como está, el usuario de dynamic_struct debe especificar el tipo de miembro al que está accediendo, siempre que lo acceda. Si ese tipo cambia, todos los de los accesos se vuelven inválidos, y causarán fallas espectaculares — o peor, fallan silenciosamente. Y no se puede atrapar en tiempo de compilación. Ese es un gran riesgo, y uno que me gustaría remediar.

Ejemplo de uso:

struct Test { 

    char a, b, c; 
    int i; 
    Foo object; 

}; 

void bar(const Test&); 

int main(int argc, char** argv) { 

    dynamic_struct<std::string> ds(sizeof(Test)); 

    ds.append<char>("a") = 'A'; 
    ds.append<char>("b") = '2'; 
    ds.append<char>("c") = 'D'; 
    ds.append<int>("i") = 123; 
    ds.append<Foo>("object"); 
    bar(ds); 

} 

Y el código siguiente:

// 
// dynamic_struct.h 
// 
// Much omitted for brevity. 
// 


/** 
* For any type, determines the alignment imposed by the compiler. 
*/ 
template<class T> 
class alignment_of { 
private: 

    struct alignment { 

     char a; 
     T b; 

    }; // struct alignment 

public: 

    enum { value = sizeof(alignment) - sizeof(T) }; 

}; // class alignment_of 


/** 
* A dynamically-created structure, whose fields are indexed by keys of 
* some type K, which can be substituted at runtime for any structure 
* with identical members and packing. 
*/ 
template<class K> 
class dynamic_struct { 
public: 


    // Default maximum structure size. 
    static const int DEFAULT_SIZE = 32; 


    /** 
    * Create a structure with normal inter-element padding. 
    */ 
    dynamic_struct(int size = DEFAULT_SIZE) : max(size) { 

     data.reserve(max); 

    } // dynamic_struct() 


    /** 
    * Copy structure from another structure with the same key type. 
    */ 
    dynamic_struct(const dynamic_struct& structure) : 
     members(structure.members), max(structure.max) { 

     data.reserve(max); 

     for (iterator i = members.begin(); i != members.end(); ++i) 
      i->second.copy(&data[0] + i->second.offset, 
       &structure.data[0] + i->second.offset); 

    } // dynamic_struct() 


    /** 
    * Destroy all members of the structure. 
    */ 
    ~dynamic_struct() { 

     for (iterator i = members.begin(); i != members.end(); ++i) 
      i->second.destroy(&data[0] + i->second.offset); 

    } // ~dynamic_struct() 


    /** 
    * Get a value from the structure by its key. 
    */ 
    template<class T> 
    T& get(const K& key) { 

     iterator i = members.find(key); 

     if (i == members.end()) { 

      std::ostringstream message; 
      message << "Read of nonexistent member \"" << key << "\"."; 
      throw dynamic_struct_access_error(message.str()); 

     } // if 

     return *reinterpret_cast<T*>(&data[0] + i->second.offset.offset); 

    } // get() 


    /** 
    * Append a member to the structure. 
    */ 
    template<class T> 
    T& append(const K& key, int alignment = alignment_of<T>::value) { 

     iterator i = members.find(key); 

     if (i != members.end()) { 

      std::ostringstream message; 
      message << "Add of already existing member \"" << key << "\"."; 
      throw dynamic_struct_access_error(message.str()); 

     } // if 

     const int modulus = data.size() % alignment; 
     const int delta = modulus == 0 ? 0 : sizeof(T) - modulus; 

     if (data.size() + delta + sizeof(T) > max) { 

      std::ostringstream message; 

      message << "Attempt to add " << delta + sizeof(T) 
       << " bytes to struct, exceeding maximum size of " 
       << max << "."; 

      throw dynamic_struct_size_error(message.str()); 

     } // if 

     data.resize(data.size() + delta + sizeof(T)); 

     new (static_cast<void*>(&data[0] + data.size() - sizeof(T))) T; 

     std::pair<iterator, bool> j = members.insert 
      ({key, member(data.size() - sizeof(T), destroy<T>, copy<T>)}); 

     if (j.second) { 

      return *reinterpret_cast<T*>(&data[0] + j.first->second.offset); 

     } else { 

      std::ostringstream message; 
      message << "Unable to add member \"" << key << "\"."; 
      throw dynamic_struct_access_error(message.str()); 

     } // if 

    } // append() 


    /** 
    * Implicit checked conversion operator. 
    */ 
    template<class T> 
    operator T&() { return as<T>(); } 


    /** 
    * Convert from structure to real structure. 
    */ 
    template<class T> 
    T& as() { 

     // This naturally fails more frequently if changed to "!=". 
     if (sizeof(T) < data.size()) { 

      std::ostringstream message; 

      message << "Attempt to cast dynamic struct of size " 
       << data.size() << " to type of size " << sizeof(T) << "."; 

      throw dynamic_struct_size_error(message.str()); 

     } // if 

     return *reinterpret_cast<T*>(&data[0]); 

    } // as() 


private: 


    // Map from keys to member offsets. 
    map_type members; 

    // Data buffer. 
    std::vector<unsigned char> data; 

    // Maximum allowed size. 
    const unsigned int max; 


}; // class dynamic_struct 
+0

Tomo la votación para cerrar es porque SO no es responsable de pe Reviso mi código y cualquier pregunta que contenga un código que requiera desplazamiento automáticamente obtiene un voto negativo. Ah bueno. –

+0

¿Qué hay de escribir esto como un artículo en CodeProject? O, si no se cierra aquí (tenerlo cerrado sería un desafortunado resultado en mi humilde opinión), entonces usted u otra persona podría atribuirle una recompensa. – John

+0

Sí, puede que tenga que buscar lugares alternativos, aunque ya estaba considerando agregar una recompensa, y si queda algo de interés en la pregunta, ciertamente lo haré. –

Respuesta

1

No hay nada inherentemente malo en este tipo de código. Retrasar la comprobación de tipos hasta que el tiempo de ejecución sea perfectamente válido, aunque deberá esforzarse para vencer el sistema de tipos de tiempo de compilación. Escribí una clase de pila homogénea, donde puedes insertar cualquier tipo que funcione de manera similar.

Sin embargo, tiene que preguntarse: ¿para qué va a usar esto en realidad? Escribí una pila homogénea para reemplazar la pila de C++ por un lenguaje interpretado, lo cual es un orden bastante difícil para cualquier clase en particular. Si no estás haciendo algo drástico, probablemente no sea lo correcto.

En resumen, puede hacerlo, y no es ilegal ni malo ni indefinido, y puede hacerlo funcionar, pero solo debe hacerlo si tiene una necesidad muy desesperada de hacer cosas fuera del alcance del lenguaje normal. Además, tu código morirá terriblemente cuando C++ 0x se convierta en Estándar y ahora necesites moverte y todo lo demás.

La manera más fácil de pensar en su código es en realidad un montón gestionado de un tamaño en miniatura. Usted coloca en varios tipos de objetos ... están almacenados contiguamente, etc.

Editar: ¿Espera, tampoco logró forzar la seguridad del tipo en el tiempo de ejecución? ¿Acabas de arruinar la seguridad del tipo de tiempo de compilación pero no la reemplazaste? Déjame publicar un código muy superior (que es algo más lento, probablemente).

Editar: Oh, espera. ¿Desea convertir su dynamic_struct, como todo, en otras estructuras arbitrarias desconocidas, en tiempo de ejecución? Oh. Oh hombre. Oh en serio. Qué. Simplemente no. Simplemente no lo hagas Realmente, realmente, no. Eso está muy mal, es increíble. Si tuviera una reflexión, podría hacer que esto funcione, pero C++ no ofrece eso. Puede aplicar seguridad de tipo en el tiempo de ejecución por cada miembro individual utilizando dynamic_cast y escriba borrado con herencia. No para toda la estructura, porque dado un tipo T no se puede decir cuáles son los tipos o el diseño binario.

+0

Este ha sido otro ejercicio para evitar que C++ se parezca más al lenguaje que estoy escribiendo, porque soy demasiado perezoso para realmente * trabajar * en el idioma. Honestamente.De todos modos, una de las supuestas ventajas de algo así como esta clase es que podría usarse para hacer conversiones automáticas más simples entre estructuras que son semánticamente compatibles pero no compatibles con binarios, lo que supongo que sería una bendición al mezclar antiguas API de C. –

+0

¿Cómo sería útil? Solo puedes convertir a nivel binario. Sería más fácil escribir sus propios operadores de elenco; no tienen que ser miembros, ¿sabe? – Puppy

+0

Sí, escriba las consideraciones de seguridad salieron por la ventana; muy mal. Creo que la forma más sencilla de solucionarlo es simplemente almacenar y probar el resultado 'typeid' para cada miembro. Los accesos no verificados serían equivalentes al viejo simple 'reinterpret_cast', si es que necesitas eso. –

1

Creo que se podría mejorar la verificación de tipos. Ahora mismo será reinterpret_cast a cualquier tipo con el mismo tamaño.

Quizás crees una interfaz para registrar las estructuras de los clientes al inicio del programa, por lo que pueden verificarse miembro por miembro, o incluso reorganizarse sobre la marcha, o construirse de forma más inteligente en primer lugar.

#define REGISTER_DYNAMIC_STRUCT_CLIENT(STRUCT, MEMBER) \ 
    do dynamic_struct::registry<STRUCT>() // one registry obj per client type \ 
     .add(# MEMBER, &STRUCT::MEMBER, offsetof(STRUCT, MEMBER)) while(0) 
     // ^name as str^ptr to memb^check against dynamic offset 
+0

Estaba pensando en soluciones basadas en macros para esto, yo mismo, y el suyo es un buen enfoque. Implicaría separar la * descripción * de un 'dynamic_struct' de la * instancia *, pero eso era algo que debía hacerse de todos modos. –

+0

@Jon: No es necesario que se produzca ninguna separación ... Estaba pensando que el método 'as', una vez que conoce el destino del elenco, consultaría el registro. Tal vez los punteros inteligentes contados por referencia podrían lograr una separación parcial, y lo mejor de ambos mundos. – Potatoswatter

+0

Mis pensamientos exactamente: has confundido mi significado. Tal como está, el miembro 'members' de' dynamic_struct' es, bueno, un miembro. Debería estar estático viviendo en el registro y compartido entre instancias 'dynamic_struct'. Eso es todo. –

0

Tengo una pregunta: ¿qué obtienes de ella?

quiero decir que es una ingeniosa pieza de código, sino:

  • que está jugando con la memoria, las posibilidades de golpe en marcha son enormes
  • es bastante complicado también, no he tenido todo y Sin duda tiene que suponer más tiempo ...

lo que realmente estoy preguntando es lo que realmente quiere ...

Por ejemplo, el uso de Boost.Fusion

struct a_key { typedef char type; }; 
struct object_key { typedef Foo type; }; 

typedef boost::fusion< 
    std::pair<a_key, a_key::type>, 
    std::pair<object_key, object_key::type> 
> data_type; 

int main(int argc, char* argv[]) 
{ 
    data_type data; 
    boost::fusion::at_key<a_key>(data) = 'a'; // compile time checked 
} 

Usando Boost.Fusion obtienes la reflexión en tiempo de compilación así como el embalaje correcto.

Realmente no veo la necesidad de seleccionar "tiempo de ejecución" aquí (usando un valor como clave en lugar de un tipo) cuando necesita pasar el tipo correcto a la asignación (char contra Foo).

Por último, tenga en cuenta que esto puede ser automatizado, gracias a la programación del preprocesador:

DECLARE_ATTRIBUTES(
    mData, 
    (char, a) 
    (char, b) 
    (char, c) 
    (int, i) 
    (Foo, object) 
) 

No mucho prolijo que una declaración típica, aunque a, b, etc ... será tipos internos en lugar de nombres de los atributos .

Esto tiene varias ventajas sobre su solución:

  • tiempo de compilación comprobar
  • cumplimiento perfecto con defecto generados constructores/constructores de copia/etc ...
  • representación mucho más compacta
  • sin búsqueda en tiempo de ejecución del miembro "correcto"
Cuestiones relacionadas