Variable sized class - C++
-
06-07-2019 - |
Question
I've seen a class which is a class which is defined like this..
class StringChild : public StringBase
{
public:
//some non-virtual functions
static StringChild* CreateMe(int size);
private:
unsigned char iBuf[1];
};
The static factory function has the following implementation..
return new(malloc(__builtin_offsetof(StringChild ,iBuf[size]))) StringChild();
So as far as I understand it this function is using placement new to extend this class.
Is this safe only because there is only 1 member and it's allocated on the heap?
Solution
It's an old C trick that was used to work around the non-availablity of variable length arrays in plain C. Yes, it also works in C++ as long as you use suitable allocator constructs (like allocating a bunch of raw memory the desired size and then placement newing the object in there). It's safe as long as you don't wander over the end of the allocated memory, but it does tend to confuse at least some memory debuggers.
One thing you have to make absolutely certain when using this technique is that the variable length array is the last element in the object layout, otherwise you'll walk over other internal variables.
I am however a little dubious about the implementation of the factory function - I assume the 'size' parameter is actually the desired array size? Also, don't forget that you'd have to release the memory above using 'free' and not 'delete', even though the latter might work in most cases.
Unless there's a compelling reason as to why the memory has to be managed this way, I would simply replace the array with a std::vector.
OTHER TIPS
This should be OK for PODs provided iBuf is the last member of the structure. The problems with non-PODs could be that eg. compiler is free to reorder public/private/protected members, virtual base classes end up at the end of the most derived object IIUC, etc.
Your structure is non-POD (it has a base class) so I wouldn't recommend it.
Also, if you create instances like this
return new(malloc(__builtin_offsetof(StringChild ,iBuf[size]))) StringChild();
You should make sure that memory acquired by malloc should be freed with free, so delete your instances like this:
obj->~StringChild();
free(obj);
Maybe you'd like to use ::operator new()
for allocation
Strictly speaking, since StringChild
is derived from StringBase
it's not safe. The C++ standard does not specify the layout for base class subobjects. Clause 10 Paragraph 3:
The order in which the base class subobjects are allocated in the most derived object (1.8) is unspecified.
If StringChild
were a POD struct, then such a technique would be safe.