In standard C you can end a struct with an array of size 0 and then over allocate it to add a variable length dimension to the array:
struct var
{
int a;
int b[];
}
struct var * x=malloc(sizeof(var+27*sizeof(int)));
How can you do that in C++ in a standard (portable) way? It is okay to have a constraint of max posible size and obviously doesn't have to work on the stack
I was thinking of:
class var
{
...
private:
int a;
int b[MAX];
};
and then use allocators or overload new/delete to under allocate based on the required size:
(sizeof(var) - (MAX-27)*sizeof(int)
But, while it seems to work, its not something I'd want to have to maintain.
Is there a cleaner way that is fully standard/portable?
In computer programming, a variable-length array (VLA), also called variable-sized or runtime-sized, is an array data structure whose length is determined at run time (instead of at compile time). In C, the VLA is said to have a variably modified type that depends on a value (see Dependent type).
Nothing you can do will guarantee that a struct will have the same size on all possible implementations. About the closest you can probably get would be something like: struct my_struct { char contents[some_size]; }; This at least guarantees that the contents will be the same size on all implementations.
We cannot directly extend structs but rather use a concept called composition where the struct is used to form other objects.
A variable length array, which is a C99 feature, is an array of automatic storage duration whose length is determined at run time.
What's wrong with simply doing a variant of the C way?
If the structure has to remain purely POD, the C way is fine.
struct var
{
int a;
int b[1];
static std::shared_ptr<var> make_var(int num_b) {
const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
return std::shared_ptr<var>(
new char[sizeof(var)+extra_bytes ],
[](var* p){delete[]((char*)(p));});
}
since it's a POD, everything works just like it did in C.
If b
is not guaranteed to be POD, then things get more interesting. I haven't tested any of it, but it would look more or less like so. Note that make_var
relies on make_unique
, because it uses a lambda destructor. You can make it work without this, but it's more code. This works just like the C way, except it cleanly handles variable amounts of types with constructors and destructors, and handles exceptions
template<class T>
struct var {
int a;
T& get_b(int index) {return *ptr(index);}
const T& get_b(int index) const {return *ptr(index);}
static std::shared_ptr<var> make_var(int num_b);
private:
T* ptr(int index) {return static_cast<T*>(static_cast<void*>(&b))+i;}
var(int l);
~var();
var(const var&) = delete;
var& operator=(const var&) = delete;
typedef typename std::aligned_storage<sizeof(T), std::alignof(T)>::type buffer_type;
int len;
buffer_type b[1];
};
template<class T> var::var(int l)
:len(0)
{
try {
for (len=0; len<l; ++len)
new(ptr(i))T();
}catch(...) {
for (--len ; len>=0; --len)
ptr(i)->~T();
throw;
}
}
template<class T> var::~var()
{
for ( ; len>=0; --len)
ptr(i)->~T();
}
template<class T> std::shared_ptr<var> var::make_var(int num_b)
{
const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
auto buffer = std::make_unique(new char[sizeof(var)+extra_bytes ]);
auto ptr = std::make_unique(new(&*buffer)var(num_b), [](var*p){p->~var();});
std::shared_ptr<var> r(ptr.get(), [](var* p){p->~var(); delete[]((char*)(p));});
ptr.release();
buffer.release;
return std::move(r);
}
Since this is untested, it probably doesn't even compile, and probably has bugs. I'd normally use std::unique_ptr
but I'm too lazy to make proper standalone deleters, and unique_ptr
is hard to return from a function when the deleter is a lambda. On the off chance you want to use code like this, use a proper standalone deleter.
While this is not directly answering your question -- I would point to that a better practice in C++ is to use the STL lib for this sort of variable length array -- it is safe and simpler and understood by anybody who will maintain it after you.
class var
{
...
private:
int a;
std::vector<int> b; // or use std::deque if more to your liking
};
Now you can just new it up like any other class;
var* myvar = new var;
And you can use it just like a old type array without explicitly allocating the memory (although that is not what most ++ programmers do)
myvar->b[0] = 123;
myvar->b[1] = 123;
myvar->b[2] = 123;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With