Reputation: 148890
Disclaimer: I already know that raw arrays are not first class elements in C++ and that in many places, we are expected to replace them with vectors. But I still hope an other way...
I am trying to build a multi-dimensional container library using contiguous data instead of vectors of vectors. A simple analogy is a 2D C array versus an array of pointers. The multi-dimensional part is handled as proxy classes over a 1D array. It implies the use of proxy iterators but my current problem is the handling of the underlying 1D array.
Ideally, that 1D array could use either static or automatic memory (and should not be freed after use) or private dynamic memory that should be. So far so good, I currently use a boolean (owning
) to remember whether the array should be deleted. I need to
manually manage that memory, because I want sub-arrays to re-use the memory of their containers and cannot use the copy semantics of standard containers.
If the underlying data type has a default contructor, everything is fine, I can safely build a dynamic array with new[]
, and later free it with delete[]
. But I also wonder whether it could be possible to only require a copyable type, and initialize the array with a default value (what std::vector
can do).
How can I build a dynamic array of a non default constructible objects and instead use copy initialization from a default value ?
Provided a default ctor is available for class T, I can write:
template <typename T>
class Holder
{
size_t sz; // size of the array
bool owning; // true if the array should be deleted
T* data; // pointer to the underlying array
public:
// builds an array of default initialized data
Holder(size_t sz) : sz(sz), owning(true) {
data = new T[sz];
}
// uses an existing array. Do not own it by default but can steal
// it if own is true
Holder(T* data, size_t sz, bool own=false) : sz(sz),
owning(own), data(data) {};
// builds an array of data initialized from a default value
Holder(const T&value, size_t sz) : sz(sz), owning(true) {
// what can be done here if no default ctor is available?
}
~Holder() {
if (owning) {
delete[] data;
}
}
// Copy ctor. Will borrow the original array
Holder(const Holder<T>& other) : sz(other.sz), owning(false), data(other.data){}
// Move ctor
Holder(Holder&& other) : sz(other.sz), owning(other.owning) {
data = other.data;
other.data = nullptr;
other.owning = false;
}
// assignment operators and accessors omitted for brievety
...
};
I also know that I can safely fill an unitialized array with std::uninitialized_fill
, and later destroy its objects with std::destroy
but I am unsure of how to correctly use that and whether it is compatible with new[]
and delete[]
. Said differently can I just delete
an array allocated with operator new[]
and initialized with uninitialized_fill
?
Upvotes: 2
Views: 248
Reputation: 62636
I am unsure of how to correctly use that and whether it is compatible with
new[]
anddelete[]
.
It isn't compatible with new T[]
, but it is compatible with new char[]
.
template <typename T>
class Holder
{
size_t sz; // size of the array
bool owning; // true if the array should be deleted
T* data; // pointer to the underlying array
using storage_t = std::aligned_storage_t<sizeof(T), alignof(T)>;
static T* alloc(size_t sz) { // or just use an Allocator
return reinterpret_cast<T*>(reinterpret_cast<char*>(new storage_t[sz]));
}
public:
// builds an array of default initialized data
Holder(size_t sz) requires std::default_initializable<T> : sz(sz), owning(true), data(alloc(sz)) {
std::uninitialized_default_construct_n(data, sz);
}
// uses an existing array. Do not own it by default but can steal
// it if own is true
// This is now suspect, we should take a deleter here.
// Holder(T* data, size_t sz, bool own=false) : sz(sz),
// owning(own), data(data) {};
// uses an existing array. Do not own it
Holder(T* data, size_t sz) : sz(sz), owning(false), data(data) {};
// uses an existing array. Do not own it
template <size_t N>
Holder(T (&data)[N]) : sz(N), owning(false), data(data) {};
// builds an array of data initialized from a default value
Holder(const T&value, size_t sz) requires std::copy_constructible<T> : sz(sz), owning(true), data(alloc(sz)) {
std::uninitialized_fill_n(data, sz, value);
}
~Holder() {
if (owning) {
std::destroy_n(data, sz);
delete[] reinterpret_cast<storage_t*>(data);
}
}
// Copy ctor. Will borrow the original array
Holder(const Holder<T>& other) : sz(other.sz), owning(false), data(other.data){}
// Move ctor
Holder(Holder&& other) : data(std::exchange(other.data, nullptr)) sz(other.sz), owning(std::exchange(other.owning, false)) {
}
// assignment operators and accessors omitted for brievety
...
};
Upvotes: 1