AlexeiBarnes
AlexeiBarnes

Reputation: 87

Handling an uninitialized array

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

Answers (1)

Richard Hodges
Richard Hodges

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

Related Questions