Reputation: 87
As part of a larger project I decided to work on a C++ c array wrapper, obviously this could instead just be a std vector, but I've learned a lot working on this and it's brought up a few points I'd like to explore further.
After posting my code on code review I have increaced the complexity of the code. Mostly this has improved the code, but I've also gained some new problems along the way:
Right now the code uses overloaded new to avoid having to default initialize members.
Simple_Array(size_type size) :
_size(size),
_data(static_cast<pointer>(::operator new(_size * sizeof(value_type))))
{}
template <
class It,
class = typename std::enable_if< std::is_constructible< value_type,
typename std::iterator_traits<It>::value_type >::value >::type
>
Simple_Array(It first, It last) :
Simple_Array(std::distance(first, last))
{
for (iterator i = _data; first != last; ++first, ++i)
new (i) value_type{*first};
}
Because of this the destructor must be called per member:
~Simple_Array() {
for (iterator i = begin(); i != end(); ++i)
i->~value_type();
::operator delete(_data);
}
The problem here is best illustrated if you try to create a Simple_Array<Simple_Array<int>>
, during destruction of such a type, if any member is not initialized then when the uninitialized Simple_Array has it's destructor called it will start playing with uninitialized iterators and cause all kinds of problems. (Note, begin() simply returns _data, and end() returns _data + _size. iterator is a Ty*, _data is a Ty* (c array), and _size is an std::size_t.)
My question is how can I handle this situation of destroying uninitialized members? Am I better off accepting that members require a default constructor?
Upvotes: 1
Views: 230
Reputation: 69892
No, you don't need to require objects to be default-constructible.
What you must do is separate the concerns of memory allocation and object initialisation.
This is one of the few legitimate uses of in-place constructors and destructors.
Don't forget to handle the destructor cleanly!!
the constructor should look something like this:
SimpleArray(Iter first, Iter last)
: _size(last - first)
, _data(allocate_aligned_uninitialised_memory()) // <- you need to figure this one out
{
// now we have allocated memory by no copied objects...
auto dest = &_data[0];
try {
while (first != last) {
// in-place construction
new (dest)(*first++);
++dest;
}
}
catch(...) {
// if one of the constructors threw, we must unwind the ones that didnt
while (dest != first) {
// in-place destruction
--dest;
dest->~dest();
}
// release the aligned memory
release_aligned_memory(_data);
// pass the exception on - we failed to construct
throw;
}
}
Upvotes: 1