Compatible variable length structure in C ++

In standard C, you can end the structure with an array of size 0, and then select it to add the size of the variable to the array:

struct var { int a; int b[]; } struct var * x=malloc(sizeof(var+27*sizeof(int))); 

How can you do this in C ++ in a standard (portable) way? It’s normal to have a maximum size limit and obviously you don’t need to work on the stack

I thought:

 class var { ... private: int a; int b[MAX]; }; 

and then use the dispensers or reload the new / delete according to the required size:

(sizeof (var) - (MAX-27) * sizeof (int)

But, although it seems to be working, its not what I want to support.

Is there a cleaner way that is completely standard / portable?

+6
source share
8 answers

What is wrong just by executing a variant of path C?

If the structure should remain purely POD, path C 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 this is a POD, everything works the same as in C.


If b not guaranteed by POD, then things get more interesting. I have not tested anything, but it would look something like this. Note that make_var relies on make_unique because it uses a lambda destructor. You can make it work without it, but this is more code. This works the same as the C path, except that it purely handles type variables 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 errors. I usually use std::unique_ptr , but I'm too lazy to make the correct standalone removers, and unique_ptr hard to return from a function when the deleter is lambda. If you want to use such code, use the correct standalone deaeer.

+3
source

Although this does not directly answer your question - I would point out that a better practice in C ++ is to use the STL library for such a variable-length array - it is safe and simpler and understandable to anyone who will support 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 update it like any other class;

 var* myvar = new var; 

And you can use it the same way as the old array of types without explicit memory allocation (although this is not what most ++ programmers do)

 myvar->b[0] = 123; myvar->b[1] = 123; myvar->b[2] = 123; 
+2
source

Yes you can, although you cannot declare it as a member of an array. You can use the link:

 struct s { int ( & extra_arr )[]; s() : extra_arr( reinterpret_cast< int (&)[] >( this[1] ) {} }; 

In practice, this will use the value of the storage pointer, although theoretically this is not necessary. This class is not a POD, due to the difference between theory and practice.


You can alternately put reinterpret_cast in a non-statistical member function:

 struct s { int ( & get_extra() )[] { return reinterpret_cast< int (&)[] >( this[1] ); } int const ( & get_extra() const )[] { return reinterpret_cast< int const (&)[] >( this[1] ); } }; 

Now access requires the syntax of the function call (the insert eliminates the difference in machine codes, except for the debug build), but there is no lost storage, and the object will be a POD prohibiting any other exception to the POD rules.

With a little customization of ABI, such as #pragma pack , you can get full compatibility with C. Often this setting is necessary for serialization applications.

Support for const-correctness is also supported, while the previous solution allows you to modify the const object (since it does not know that the array is part of the same object).

A wrapper can be generalized to a CRTP base class (which in C ++ 11 even still allows a derived class to be a POD) or a preprocessor macro that expands to define either C ++ access or a C element of flexibility.


Note that none of these solutions do anything except the original C. Special member functions will not copy the flexible array, and the class cannot support a function parameter or subobject.

+1
source

A cleaner way is to use inheritance:

 class Parent { public: virtual int get_b(unsigned int index) = 0; protected: int a; }; class Child1 : public Parent { public: int get_b(unsigned int index) { return b[index]; // Should have index bounds checking. } private: int b[20]; }; 

Inheritance allows you to customize the size and number of members of the Parent class.

0
source

Good - (without questioning it, as I'm not sure about that). Since it seems to me that from the current answers there is no better way to put on top , I was wondering if this would help maintain reasonable service:

 template <class BASE, class T> class dynarray { public: BASE base; const size_t size; T data[1]; // will be over allocated static dynarray * create(size_t data_size) { return new(data_size) dynarray(data_size); } void operator delete(void *p) { ::operator delete(p); } private: void * operator new (size_t full_size, size_t actual) { if (full_size != sizeof(dynarray)) { // inheritence changed size - allocate it all return ::operator new(sizeof(dynarray)); } return ::operator new(sizeof(dynarray) + (actual-1)*sizeof(T)); } void operator delete(void *p, size_t) // matching delete { ::operator delete(p); } dynarray(size_t data_size) : size(data_size) { } }; 

use is a bit unnatural, but maybe better:

 typedef dynarray<double,int,27> dyn; dyn * x=dyn::create(7); x->data[5]=28; x->base=5.3; 

EDIT: implementation has changed from in alloaction to over .

0
source

Another way to do this is to use a new placement

 #include <cstdlib> class var { ... private: int a; int b[1]; }; var * x = new(malloc(sizeof(var) + (27-1)*sizeof(var::b))) var; 

in this case, the contructor is called in the allocated memory

to remove structure usage:

 x->~var(); // only if var have a destructor free(x); 

or better, add the delete operator to var and use delete:

 struct var { ... operator delete(void* ptr) throw() { free(ptr); } }; var * x = ... delete x; 

The best and correct approach is to use a static function to instantiate and privatize the constructor: class var {public: ... static var * create (int size,) throw () {new (malloc (sizeof (var) + (27-1) * sizeof (b)))) var (); } void operator delete (void * ptr) {free (ptr); }

 private: int a; int b[1]; var(<args>) { ... } }; var * x = var::create(27); delete x; 

note: I use an array of size 1 because undeffined and 0 dimensional arrays are not supported by all compilers.

0
source
 template <size_t MAX> class var { ... private: int a; int b[MAX]; }; 

In each instance of the template, MAX is a constant that can be used in loops. Then you can build vars of any length.

 var<7> v7; var<100> v100; 

Or print them

 typedef var<10> myVar; 
0
source

A defect report was published which was listed here for C: http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_051.html

The answer is that the declaration of the array will be the largest size you need (or, in fact, you can also make the maximum possible size for an integer) - this is a "safer idiom" and that it strictly matches. The idea is that instead of allocating and therefore working outside the declared size of the array, you actually allocate and only access memory inside the declared boundaries of the array.

This should apply to C ++ just as if it did not change those rules that it did not have, because it meant that it was pretty much compatible with C. If someone knows something specific to C ++, which makes this solution invalid, let me know.

As long as you hide this implementation behind a well-defined interface, there should be no problems with maintenance.

-2
source

Source: https://habr.com/ru/post/958041/


All Articles