I'm trying to create a customized replacement for std::vector where I know that the vast majority of the time, the size will be <= 3. On rare occasions, I could have up to 32 integers. This is a performance and memory critical vector and needs to be copied frequently, so I want to avoid the allocations for the 99.9% case.
I am using 64-bit compilers on Linux and Windows (gcc & msvc) with c++17. int is known to be 32-bit. If we ever need to support something different, I'll revisit the code.
I'd essentially like this memory structure:
class SmallIntVector
{
int m_size;
union
{
int m_data[3]; // Used when m_size <=3
int* m_dataDynamic; // Used when m_size >=4
};
};
However, the size of this is 24 due to the alignment. I can do this:
class SmallIntVector
{
int m_size;
int m_data0; // first data member for m_size <= 3. Return &m_data0 and treat it as data[3].
union
{
int m_data12[2]; // next two data members.
int* m_dataDynamic; // Used with m_size>=4
};
};
But, I'm pretty sure this is UB because of aliasing memory. I could also do this:
struct ForStatic
{
int m_size;
int m_data[3];
};
struct ForDynamic
{
int m_unused; // Don't use this to avoid punning.
int *m_dataDynamic; // Pointer, must be at an 8 byte offset
};
class SmallIntVector
{
union
{
ForStatic m_stat; // When m_stat.m_size is <=3, use m_stat.m_data.
ForDynamic m_dyn; // When m_stat.m_size is >=4, use m_dyn.m_data.
};
};
The size is 16, but I think this is illegal punning of the union: I'd be using m_size from ForStatic while at the same time using m_dataDynamic from ForDynamic.
I can easily avoid UB by using std::memcpy to read the dynamic pointer, but none of this feels right. Is there any way to get the alignment I'm looking for and avoid UB?
I've omitted all of the member functions -- I think they are obvious once the structure is right.