template<class T, class D>
struct var_array_at_end {
var_array_at_end( std::size_t N ) {
::new( (void*)data() ) std::aligned_storage_t<sizeof(T)*N, alignof(T)>;
for (std::size_t i = 0; i < N; ++i) {
::new( (void*)( data()+sizeof(T)*i) ) ) T();
}
}
char* data() { return reinterpret_cast<char*>(this)+sizeof(D); }
char const* data() const { return reinterpret_cast<char*>(this)+sizeof(D); }
T* ptr(std::size_t i = 0) { return reinterpret_cast<T*>( data()+sizeof(T)*i ); }
T const* ptr(std::size_t i = 0) const { return reinterpret_cast<T*>( data()+sizeof(T)*i ); }
T& operator[](std::size_t n) {
return *ptr(n);
}
T const& operator[](std::size_t n) const {
return *ptr(n);
}
};
struct MyData:
var_array_at_end<int, MyData>
{
private:
explicit MyData( int count ):
var_array_at_end<int, MyData>( count>0?(unsigned)count:0 ),
noOfItems(count)
{}
struct cleanup {
void operator()(MyData* ptr) {
char* buff = reinterpret_cast<char*>(ptr);
ptr->~MyData();
delete[] buff;
}
};
public:
using up = std::unique_ptr<MyData*, cleanup>;
static up create( int count ) {
if (count < 0) count = 0;
std::unique_ptr<char> buff = std::make_unique<char[]>( sizeof(MyData)+sizeof(int)*count );
auto* ptr = ::new( (void*)buff.get() ) MyData( count );
(void)buff.release();
return up( ptr, {} );
}
int noOfItems;
};
MyData * getData(int size)
{
return MyData::create(size).release(); // dangerous, unmanaged memory
}
I believe this is standard compliant, assuming your implementation doesn't add padding on arrays of trivial types (like char). I am unware of any implementation thta does.
I didn't assume that MyData
only contains plain old data; you could stuff a std::vector
into it with the above. I might be able to simplify a few lines with that assumption.
It is more than a bit of a pain.
auto foo = MyData::create(100)
creates a unique ptr to MyData
that has a buffer of 100 int
s after it. (*foo)[77]
accesses the 77th element of the buffer.
Due to a defect in the standard, you there isn't an array after MyData
but rather a buffer containing 100 distinct int
objects in adjacent memory locations. There are vrey annoying differences between those two things; naive pointer arithimetic is guaranteed to work within arrays, but not between packed adjacent int
s in a buffer. I am unaware of a compiler that enforces that difference.