Variable Sized Struct C ++

¿Es esta la mejor manera de hacer una estructura de tamaño variable en C ++? No quiero usar vectores porque la longitud no cambia después de la inicialización.

struct Packet { unsigned int bytelength; unsigned int data[]; }; Packet* CreatePacket(unsigned int length) { Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int)); output->bytelength = length; return output; } 

Editar: nombres de variables renombradas y código cambiado para ser más correcto.

Algunas reflexiones sobre lo que estás haciendo:

  • El uso del estilo de estructura de longitud variable C-style le permite realizar una asignación de tienda gratuita por paquete, que es la mitad de lo que sería necesario si struct Packet contuviera un std::vector . Si está asignando una gran cantidad de paquetes, entonces realizar la mitad de las asignaciones / desasignaciones de tiendas gratuitas puede ser muy importante. Si también está haciendo accesos a la red, entonces el tiempo dedicado a esperar a la red probablemente será más significativo.

  • Esta estructura representa un paquete. ¿Planeas leer / escribir desde un socket directamente en un struct Packet ? Si es así, probablemente deba considerar la orden de bytes. ¿Va a tener que convertir de un host a un orden de bytes de red cuando envía paquetes, y viceversa cuando recibe paquetes? Si es así, entonces podría byte-swap los datos en su estructura de longitud variable. Si convirtió esto para usar un vector, tendría sentido escribir métodos para serializar / deserializar el paquete. Estos métodos lo transferirían a / desde un búfer contiguo, teniendo en cuenta el orden de bytes.

  • Del mismo modo, es posible que deba tener en cuenta la alineación y el embalaje.

  • Usted nunca puede subclasificar Packet . Si lo hiciera, las variables de miembro de la subclase se superpondrían con la matriz.

  • En lugar de malloc y free , puede usar Packet* p = ::operator new(size) y ::operator delete(p) , ya que struct Packet es un tipo POD y actualmente no se beneficia de tener su constructor predeterminado y su destructor llamado . El beneficio (potencial) de hacerlo es que el operator new global operator new maneja los errores usando el nuevo manejador global y / o excepciones, si eso es importante para usted.

  • Es posible hacer que la estructura de estructura variable trabaje con los operadores nuevos y eliminados, pero no así. Puede crear un operator new personalizado operator new que tome una longitud de matriz implementando static void* operator new(size_t size, unsigned int bitlength) , pero igual tendrá que establecer la variable de miembro de longitud de bit. Si hiciste esto con un constructor, podrías usar la expresión ligeramente redundante Packet* p = new(len) Packet(len) para asignar un paquete. El único beneficio que veo en comparación con el uso de operator new global operator new y la operator delete sería que los clientes de su código podrían simplemente llamar delete p lugar de ::operator delete(p) . Envolver la asignación / desasignación en funciones separadas (en lugar de llamar a delete p directamente) está bien siempre que se llamen correctamente.

Si nunca agrega un constructor / destructor, los operadores de asignación o las funciones virtuales a su estructura usando malloc / free para la asignación son seguros.

Está mal visto en círculos de C ++, pero considero que su uso está bien si lo documentas en el código.

Algunos comentarios a tu código:

 struct Packet { unsigned int bitlength; unsigned int data[]; }; 

Si recuerdo bien, declarar una matriz sin una longitud no es estándar. Funciona en la mayoría de los comstackdores pero puede advertirte. Si quiere cumplir, declare su matriz de longitud 1.

 Packet* CreatePacket(unsigned int length) { Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int)); output->bitlength = length; return output; } 

Esto funciona, pero no se tiene en cuenta el tamaño de la estructura. El código se romperá una vez que agregue nuevos miembros a su estructura. Mejor hazlo de esta manera:

 Packet* CreatePacket(unsigned int length) { size_t s = sizeof (Packed) - sizeof (Packed.data); Packet *output = (Packet*) malloc(s + length * sizeof(unsigned int)); output->bitlength = length; return output; } 

Y escriba un comentario en la definición de estructura de su paquete de que los datos deben ser el último miembro.

Por cierto, la asignación de la estructura y los datos con una sola asignación es algo bueno. De esta forma, se reduce a la mitad el número de asignaciones y también se mejora la localidad de los datos. Esto puede mejorar el rendimiento bastante si asigna muchos paquetes.

Lamentablemente, c ++ no proporciona un buen mecanismo para hacer esto, por lo que a menudo terminas con dichos hacks malloc / free en aplicaciones del mundo real.

Esto está bien (y era una práctica estándar para C).

Pero esta no es una buena idea para C ++.
Esto se debe a que el comstackdor genera un conjunto completo de otros métodos automáticamente para usted alrededor de la clase. Estos métodos no entienden que has hecho trampa.

Por ejemplo:

 void copyRHSToLeft(Packet& lhs,Packet& rhs) { lhs = rhs; // The compiler generated code for assignement kicks in here. // Are your objects going to cope correctly?? } Packet* a = CreatePacket(3); Packet* b = CreatePacket(5); copyRHSToLeft(*a,*b); 

Use std :: vector <> es mucho más seguro y funciona correctamente.
También apostaría a que es tan eficiente como su implementación después de que el optimizador se active.

Alternativamente, boost contiene una matriz de tamaño fijo:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html

Puede usar el método “C” si lo desea, pero para mayor seguridad, haga que el comstackdor no intente copiarlo:

 struct Packet { unsigned int bytelength; unsigned int data[]; private: // Will cause compiler error if you misuse this struct void Packet(const Packet&); void operator=(const Packet&); }; 

Si realmente está haciendo C ++, no existe una diferencia práctica entre una clase y una estructura, excepto la visibilidad predeterminada del miembro: las clases tienen visibilidad privada de forma predeterminada, mientras que las estructuras tienen visibilidad pública de manera predeterminada. Los siguientes son equivalentes:

 struct PacketStruct { unsigned int bitlength; unsigned int data[]; }; class PacketClass { public: unsigned int bitlength; unsigned int data[]; }; 

El punto es que no necesitas el CreatePacket (). Simplemente puede inicializar el objeto struct con un constructor.

 struct Packet { unsigned long bytelength; unsigned char data[]; Packet(unsigned long length = 256) // default constructor replaces CreatePacket() : bytelength(length), data(new unsigned char[length]) { } ~Packet() // destructor to avoid memory leak { delete [] data; } }; 

Algunas cosas para tener en cuenta. En C ++, use new en lugar de malloc. Me tomé un poco de libertad y cambié la longitud de bit a bytelength. Si esta clase representa un paquete de red, será mucho mejor tratar con bytes en lugar de bits (en mi opinión). La matriz de datos es una matriz de char sin signo, no sin signo int. Nuevamente, esto se basa en mi suposición de que esta clase representa un paquete de red. El constructor te permite crear un paquete como este:

 Packet p; // default packet with 256-byte data array Packet p(1024); // packet with 1024-byte data array 

El destructor se invoca automáticamente cuando la instancia del paquete se sale del scope y evita una pérdida de memoria.

Probablemente me limitaré a usar un vector<> menos que la sobrecarga extra mínima (probablemente una sola palabra adicional o puntero sobre su implementación) realmente plantee un problema. No hay nada que diga que tienes que redimensionar () un vector una vez que ha sido construido.

Sin embargo, hay varias ventajas de ir con vector<> :

  • ya maneja correctamente la copia, la asignación y la destrucción: si tira el suyo, debe asegurarse de manejarlos correctamente
  • todo el soporte del iterador está ahí; de nuevo, no tiene que hacer el suyo propio.
  • todos ya saben cómo usarlo

Si realmente quieres evitar que la matriz crezca una vez construida, es posible que desees considerar tener tu propia clase que hereda de vector<> privadamente o tiene un vector<> miembro y solo exponer a través de métodos que simplemente utilizan los métodos vectoriales esos bits del vector que desea que los clientes puedan usar. Eso debería ayudarlo a ponerse en marcha rápidamente con bastante buena seguridad de que hay filtraciones y lo que no está allí. Si hace esto y descubre que la pequeña sobrecarga del vector no le funciona, puede volver a implementar esa clase sin la ayuda de vector y su código de cliente no debería cambiar.

Es probable que desee algo más ligero que un vector para obtener un alto rendimiento. También desea ser muy específico sobre el tamaño de su paquete para ser multiplataforma. Pero tampoco quiere preocuparse por las pérdidas de memoria.

Afortunadamente, la biblioteca de impulso hizo la mayor parte de la parte difícil:

 struct packet { boost::uint32_t _size; boost::scoped_array _data; packet() : _size(0) {} explicit packet(packet boost::uint32_t s) : _size(s), _data(new unsigned char [s]) {} explicit packet(const void * const d, boost::uint32_t s) : _size(s), _data(new unsigned char [s]) { std::memcpy(_data, static_cast(d), _size); } }; typedef boost::shared_ptr packet_ptr; packet_ptr build_packet(const void const * data, boost::uint32_t s) { return packet_ptr(new packet(data, s)); } 

Ya hay muchos buenos pensamientos mencionados aquí. Pero falta uno. Las matrices flexibles son parte de C99 y, por lo tanto, no son parte de C ++, aunque algunos comstackdores de C ++ pueden proporcionar esta funcionalidad, no hay garantía de eso. Si encuentra una forma de usarlos en C ++ de una manera aceptable, pero tiene un comstackdor que no lo admite, tal vez pueda recurrir a la forma “clásica”

Debe declarar un puntero, no una matriz con una longitud no especificada.

No hay nada incorrecto en el uso del vector para arreglos de tamaño desconocido que serán reparados después de la inicialización. En mi humilde opinión, eso es exactamente para lo que son los vectores. Una vez que lo haya inicializado, puede simular que se trata de una matriz, y debería comportarse de la misma manera (incluido el comportamiento en el tiempo).