Estructura de tamaño variable C++
variable-length (10)
Algunos pensamientos sobre lo que estás haciendo:
El uso de la estructura de longitud variable de estilo C le permite realizar una asignación de almacenamiento libre por paquete, que es la mitad de lo que se requeriría si la
struct Packet
contenía unstd::vector
. Si está asignando un gran número de paquetes, entonces la mitad de las asignaciones / desasignaciones de tiendas gratuitas pueden ser muy importantes. 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. ¿Planea leer / escribir desde un socket directamente en un
struct Packet
? Si es así, probablemente deba considerar el orden de bytes. ¿Tendrá que convertir el orden de bytes del host al de la red al enviar paquetes, y viceversa al recibir paquetes? Si es así, entonces podría intercambiar los datos en su lugar en su estructura de longitud variable. Si convierte 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 los bytes.Del mismo modo, es posible que deba tener en cuenta la alineación y el embalaje.
Nunca se puede subclasificar
Packet
. Si lo hiciera, entonces las variables miembro de la subclase se solaparían con la matriz.En lugar de
malloc
yfree
, puede usarPacket* p = ::operator new(size)
y::operator delete(p)
, ya questruct 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 eloperator new
global maneja los errores usando el nuevo controlador global y / o las excepciones, si eso es importante para usted.Es posible hacer que el lenguaje de estructura de longitud variable funcione con los operadores nuevo y de borrado, pero no bien. Podría crear un
operator new
personalizadooperator new
que tome una longitud de matriz implementandostatic void* operator new(size_t size, unsigned int bitlength)
, pero aún tendría que establecer la variable miembro bitlength. Si hizo esto con un constructor, podría usar la expresión ligeramente redundantePacket* p = new(len) Packet(len)
para asignar un paquete. El único beneficio que veo comparado con el uso deloperator new
globaloperator new
y deloperator delete
es que los clientes de su código podrían simplemente llamardelete p
lugar de::operator delete(p)
. Ajustar la asignación / desasignación en funciones separadas (en lugar de llamar adelete p
directamente) está bien siempre y cuando se llamen correctamente.
¿Es esta la mejor manera de hacer una estructura de tamaño variable en C ++? No quiero usar vector 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;
}
Edición: nombres de variables renombrados y códigos cambiados para que sean más correctos.
Debe declarar un puntero, no una matriz con una longitud no especificada.
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 compilador 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);
Usar el 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 se active el optimizador.
Alternativamente, boost contiene una matriz de tamaño fijo:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html
No hay nada de malo en utilizar vector para matrices de tamaño desconocido que se solucionarán después de la inicialización. En mi humilde opinión, para eso son exactamente los vectores. Una vez que lo haya inicializado, puede fingir que se trata de una matriz, y debería comportarse de la misma manera (incluido el comportamiento del tiempo).
Probablemente me limito a usar un vector<>
menos que la sobrecarga adicional mínima (probablemente una sola palabra adicional o un puntero sobre su implementación) realmente plantee un problema. No hay nada que diga que tienes que redimensionar () un vector una vez que se ha construido.
Sin embargo, hay varias ventajas de ir con vector<>
:
- ya maneja la copia, la asignación y la destrucción de manera adecuada: si usted hace su propio rollo, debe asegurarse de manejarlos correctamente
- todo el soporte del iterador está ahí, una vez más, no tienes que hacer el tuyo.
- todo el mundo ya sabe cómo usarlo
Si realmente desea evitar que la matriz crezca una vez construida, puede considerar tener su propia clase que hereda de vector<>
privada o tiene un miembro vector<>
y exponer solo a través de métodos que solo responden a los métodos vectoriales de esos bits. del vector que desea que los clientes puedan usar. Eso debería ayudarlo a comenzar rápidamente, con bastante buena seguridad de que las fugas y lo que no están allí. Si hace esto y descubre que la pequeña sobrecarga de vector no está funcionando para usted, puede volver a implementar esa clase sin la ayuda de vector y el código de su cliente no debería cambiar.
Probablemente quieras 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 que sea multiplataforma. Pero usted tampoco quiere preocuparse por las fugas 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<unsigned char> _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<const unsigned char * const>(d), _size);
}
};
typedef boost::shared_ptr<packet> packet_ptr;
packet_ptr build_packet(const void const * data, boost::uint32_t s)
{
return packet_ptr(new packet(data, s));
}
Puedes usar el método "C" si quieres, pero por seguridad hazlo para que el compilador 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 nunca agrega un constructor / destructor, los operadores de asignación o funciones virtuales a su estructura utilizando malloc / free para asignación es seguro.
Está mal visto en los círculos de c ++, pero considero que su uso está bien si lo documenta en el código.
Algunos comentarios a su 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 compiladores, pero puede darte una advertencia. Si desea ser compatible 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 tienes en cuenta el tamaño de la estructura. El código se romperá una vez que agregue nuevos miembros a su estructura. Mejor hacerlo 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 la estructura de su paquete de que los datos deben ser el último miembro.
Por cierto, es una buena cosa asignar la estructura y los datos con una sola asignación. Reduce a la mitad la cantidad de asignaciones de esa manera, y también mejora la localidad de los datos. Esto puede mejorar el rendimiento bastante si asigna muchos paquetes.
Desafortunadamente, c ++ no proporciona un buen mecanismo para hacer esto, por lo que a menudo terminas con hacks malloc / free en aplicaciones del mundo real.
Si realmente estás haciendo C ++, no hay una diferencia práctica entre una clase y una estructura, excepto la visibilidad de miembro predeterminada: las clases tienen visibilidad privada por defecto, mientras que las estructuras tienen visibilidad pública por defecto. 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 de estructura 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 a tener en cuenta. En C ++, use nuevo en lugar de malloc. Me he tomado un poco de libertad y he cambiado la longitud de bits a la longitud de bytel. 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 caracteres sin signo, no int sin signo. De nuevo, esto se basa en mi suposición de que esta clase representa un paquete de red. El constructor le permite crear un paquete como este:
Packet p; // default packet with 256-byte data array
Packet p(1024); // packet with 1024-byte data array
Se llama automáticamente al destructor cuando la instancia del paquete queda fuera del alcance e impide una pérdida de memoria.
Ya hay muchos buenos pensamientos mencionados aquí. Pero falta uno. Los arreglos flexibles son parte de C99 y, por lo tanto, no son parte de C ++, aunque algunos compiladores de C ++ pueden proporcionar esta funcionalidad, no hay garantía de ello. Si encuentra una manera de usarlos en C ++ de una manera aceptable, pero tiene un compilador que no lo admite, tal vez pueda recurrir a la forma "clásica"