Is this the best way to make a variable sized struct in C++? I don\'t want to use vector because the length doesn\'t change after initialization.
struct Pack
You probably want something lighter than a vector for high performances. You also want to be very specific about the size of your packet to be cross-platform. But you don't want to bother about memory leaks either.
Fortunately the boost library did most of the hard part:
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));
}
You should declare a pointer, not an array with an unspecified length.
If you never add a constructor/destructor, assignment operators or virtual functions to your structure using malloc/free for allocation is safe.
It's frowned upon in c++ circles, but I consider the usage of it okay if you document it in the code.
Some comments to your code:
struct Packet
{
unsigned int bitlength;
unsigned int data[];
};
If I remember right declaring an array without a length is non-standard. It works on most compilers but may give you a warning. If you want to be compliant declare your array of length 1.
Packet* CreatePacket(unsigned int length)
{
Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int));
output->bitlength = length;
return output;
}
This works, but you don't take the size of the structure into account. The code will break once you add new members to your structure. Better do it this way:
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;
}
And write a comment into your packet structure definition that data must be the last member.
Btw - allocating the structure and the data with a single allocation is a good thing. You halve the number of allocations that way, and you improve the locality of data as well. This can improve the performance quite a bit if you allocate lots of packages.
Unfortunately c++ does not provide a good mechanism to do this, so you often end up with such malloc/free hacks in real world applications.
Some thoughts on what you're doing:
Using the C-style variable length struct idiom allows you to perform one free store allocation per packet, which is half as many as would be required if struct Packet
contained a std::vector
. If you are allocating a very large number of packets, then performing half as many free store allocations/deallocations may very well be significant. If you are also doing network accesses, then the time spent waiting for the network will probably be more significant.
This structure represents a packet. Are you planning to read/write from a socket directly into a struct Packet
? If so, you probably need to consider byte order. Are you going to have to convert from host to network byte order when sending packets, and vice versa when receiving packets? If so, then you could byte-swap the data in place in your variable length struct. If you converted this to use a vector, it would make sense to write methods for serializing / deserializing the packet. These methods would transfer it to/from a contiguous buffer, taking byte order into account.
Likewise, you may need to take alignment and packing into account.
You can never subclass Packet
. If you did, then the subclass's member variables would overlap with the array.
Instead of malloc
and free
, you could use Packet* p = ::operator new(size)
and ::operator delete(p)
, since struct Packet
is a POD type and does not currently benefit from having its default constructor and its destructor called. The (potential) benefit of doing so is that the global operator new
handles errors using the global new-handler and/or exceptions, if that matters to you.
It is possible to make the variable length struct idiom work with the new and delete operators, but not well. You could create a custom operator new
that takes an array length by implementing static void* operator new(size_t size, unsigned int bitlength)
, but you would still have to set the bitlength member variable. If you did this with a constructor, you could use the slightly redundant expression Packet* p = new(len) Packet(len)
to allocate a packet. The only benefit I see compared to using global operator new
and operator delete
would be that clients of your code could just call delete p
instead of ::operator delete(p)
. Wrapping the allocation/deallocation in separate functions (instead of calling delete p
directly) is fine as long as they get called correctly.