Reputation: 60918
Suppose I want a variable-size array with a bit of a header up front stored in a std::shared_ptr
. I could do something like
#include <memory>
using namespace std;
struct obj {
char headerCode;
unique_ptr<short[]> data;
};
shared_ptr<obj> make(unsigned len) {
return shared_ptr<obj>{new obj{'x', unique_ptr<short[]>{new short[len]}}};
}
But this would lead to three allocations: one for the shared_ptr
control block, one for the obj
and one for its data
. With make_shared
it is possible that the first two share some memory:
#include <memory>
using namespace std;
struct obj {
char headerCode;
unique_ptr<short[]> data;
obj(char headerCode, short data[]) : headerCode(headerCode), data(data) {}
};
shared_ptr<obj> make(unsigned len) {
return make_shared<obj>('x', new short[len]);
}
With low-level allocation I can make the object and its data share some memory:
#include <memory>
#include <cstdlib>
using namespace std;
struct obj {
char headerCode;
short data[0];
};
shared_ptr<obj> make(unsigned len) {
obj* o = reinterpret_cast<obj*>(malloc(sizeof(obj) + len*sizeof(short)));
o->headerCode = 'x';
return shared_ptr<obj>(o, free);
}
Are these two techniques allowed by the standard? If not, is there something similar which is allowed? Is there something which makes this work in a standards-conforming way with only a single memory allocation? Preferrably without having to store an allocator or deleter object in every instance?
Upvotes: 1
Views: 1939
Reputation: 171343
Are these two techniques allowed by the standard?
The first one is fine, the second uses a zero-length array as a member (data[0]
) which is not valid C++, but is supported as an extension by some compilers.
Is there something which makes this work in a standards-conforming way with only a single memory allocation?
Currently it's pretty difficult without http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3641.html which would allow something like this:
struct obj {
char headerCode;
short* data;
};
shared_ptr<obj> make(unsigned len) {
auto p = make_shared<char[]>(sizeof(obj) + sizeof(short)*len));
short* s = static_cast<short*>(p.get() + sizeof(obj));
return shared_ptr<obj>(p, ::new(p.get()) obj{'x', s});
}
This allocates an array of char
with enough space for an obj
and the array of short
, then uses placement new to construct an obj
into that memory, and creates another shared_ptr
which shares ownership with the one that owns the char
array. When the last shared_ptr
that owns the memory drops its reference the char
array will be correctly deleted, but the obj
object's destructor will not be run, so it's important that you don't rely on its destructor having any side effects (ideally it will have a trivial destructor).
You might be able to do it today using allocate_shared
with a custom allocator which allocates additional space for the array of short
, but since you don't know how the implementation will arrange the control block and your object in the space allocated it's quite difficult to work out where the short
array begins, and the allocator needs to be stored in the control block.
Upvotes: 1