2

I'm using std::array to replace the 'pointer and count' calling methods in some C code I'm upgrading to C++. Creating and using the std::array is no problem, but on destruction it will call the destructor on all elements.

But my arrays are often not completely full, causing a destructor call on an empty element. Usually not a problem, as they will be constructed with the default constructor, but I'd like to just remove an element by decrementing a size count, which means leaving a 'used' element in the array, which then causes problems on destruction.

What would be the best way to make sure only a part of the array is destroyed, by having the destructor called on its elements?

Sorry, I haven't been clear. I created a class that contains a std::array and a size int.

class MyArray {
  std::array< myclass, N > m_array;
  unsigned int             m_size;
};

The array will not always be full, obviously. I need a destructor that will only destroy the first m_size elements of m_array.

Would this work?

MyArray::~MyArray() {
  for (unsigned int s = 0; s < m_size; ++s) {
    m_array[s].~myclass();
  }
}

My fear is the destructor being called twice on myclass.

MvC
  • 21
  • 3
  • 2
    `std::array` doesn't support removing elements. Perhaps you could use a `std::vector` instead? – Bo Persson Mar 02 '16 at 12:34
  • 1
    It would be better to show a code sample instead of providing lengthy explanations. Well, if a `std::array` is destructed of course all of the contained elements are destructed as well. – πάντα ῥεῖ Mar 02 '16 at 12:35
  • 1
    Trying to defeat C++ is generally a very bad idea. When the array gets destroyed, its elements have no reason to stay alive. – Sergey Kalinichenko Mar 02 '16 at 12:36
  • Could you please explain "pointer and count" ? Sounds like reference counting, but it's not exactly clear what you want to do. – SirDarius Mar 02 '16 at 12:37
  • You can empty an object's containers which is just as good. eg by move assigning an empty object to it. – QuentinUK Mar 02 '16 at 12:38
  • @Bo Persson I realise array has a fixed size, but I cannot have the overhead of vector in my application. I just want to be able to use a part of the array. – MvC Mar 02 '16 at 12:38
  • 'pointer and count' is a way to pass C style arrays with a size count. Quite common in C. Perhaps I should call it 'pointer and size'. – MvC Mar 02 '16 at 12:40
  • 1
    This exactly what `std::vector` is. A class containing an array and its size, as well as capacity. I believe any class you might write to obtain similar functionality would have as much overhead as the already well-optimized vector. – SirDarius Mar 02 '16 at 12:49
  • You might use an array of std::experimental::optional / boost::optional –  Mar 02 '16 at 12:50
  • 2
    "The array will not always be full, obviously." <-- then you don't want an array, or at least not an array of objects (maybe an array of optionals) – Barry Mar 02 '16 at 15:45

4 Answers4

4

That's completely impossible. A std::array is just a wrapper around a raw array. An array has no notion of being "full" or "empty". The elements are likewise just there, they cannot be "removed", and they will be destructed.

You are fighting against the main characteristics of the container type you've chosen. You must revert the choice. There must be countless other ways of implementing the behaviour you need, e.g. with std::vector.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
3

Start with this:

template<class T, size_t N>
struct semirray{
  size_t cur=0;
  std::array<std::aligned_storage_t<sizeof(T),alignof(T)>,N> buf;
};

Then implement resizing, ctors and dtors on the semirray class, using placement new and manual destruction. The buf will contain empty data that has enough room for a T and is aligned properly.

This is work.

If you are replacing a pointer-and-count, consider using a std::vector<T>. This takes up the space of 1 pointer and 2 counts (or 3 pointers) usually. If you want objects not to move, you .reserve(x) the buffer size, and now size() is the initialized size and .capacity() is the uninitialized size. So long as you don't pass capacity, item location is stable.

If you don't care about the items location in memory, you don't even have to worry much about capacity.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Yes, I do realise that std::vector is usually the best solution, but I need to create large amounts of these arrays and I'm already using too much memory as it is. For me every byte counts, so vector is not the best solution. std::array works fine for me, except that I now have to 'clear' every element that I 'remove', just so that the destructor doesn't crash. Not having to do that, and not having all these destructor calls would speed things up. Memory usage and speed are extremely critical for us. – MvC Mar 03 '16 at 10:45
  • @MvC Why do you think vector uses more memory than std::array? Usually a vector would use _less_ memory than a std::array. If you're worried about heap overhead, then [make the vector use the stack](http://stackoverflow.com/questions/8049657/stack-buffer-based-stl-allocator) – Mooing Duck Jun 03 '16 at 18:12
2

If your array isn't always full, then it's either not an array or not an array of objects. An array of N myclasses always contains N myclasses.

You probably want one of:

  • std::vector<myclass> - a dynamically sized array, that will keep track of all of its own elements, and delete them as appropriate.
  • std::array<boost::optional<myclass>, N>> - a statically sized array of elements that could be optional - each element in the array knows whether or not it is an actual object, and will only call ~myclass() if it is.

Given this comment to lead off your question:

I'm using std::array to replace the 'pointer and count' calling methods in some C code I'm upgrading to C++

I believe you want std::vector<myclass>. It's like a pointer and a count, except way better.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • The array of optionals is quick to write, but has a bunch of extra cost due to extra semantics that the OP does not require (a per-element "is this element valid") – Yakk - Adam Nevraumont Mar 02 '16 at 16:18
0

I believe you can use the union trick.

A union will not call destructors on its members since it can not know which ones to call. Therefore you need to explicitly destruct the elements.

#include <memory>
#include <cstddef>
#include <algorithm>
#include <iterator>

tmeplate<typename T, std::size_t N>
struct Partial_array
{
    // Anonymous union escapes the names of its members into the outer scope
    union {std::array<T, N> array_;};
    std::size_t num_elements_;

    // Need explicit copy  / move constructors

    ~Partial_array()
    {
        // C++17
        std::destroy(std::begin(array_), std::begin(array_) + num_elements_)

        // C++11
        std::for_each(std::begin(array_), std::begin(array_) + num_elements_,
            [](const auto& element) -> void {element.~T();}
        );
    }
};
Judge Pepe
  • 21
  • 2