Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inlining an array of non-default constructible objects in a C++ class

C++ doesn't allow a class containing an array of items that are not default constructible:

class Gordian {
  public:
    int member;
    Gordian(int must_have_variable) : member(must_have_variable) {}
};

class Knot {
  Gordian* pointer_array[8]; // Sure, this works.
  Gordian inlined_array[8]; // Won't compile. Can't be initialized.
};

As even beginning C++ users know, the language guarantees that all non-POD members are initialized when constructing a class. And it doesn't trust the user to initialize everything in the constructor - one has to provide valid arguments to the constructors of all members before the body of the constructor even starts.

Generally, that's a great idea as far as I'm concerned, but I've come across a situation where it would be a lot easier if I could actually have an array of non-default constructible objects.

The obvious solution: Have an array of pointers to the objects. This is not optimal in my case, as I am using shared memory. It would force me to do extra allocation from an already contended resource (that is, the shared memory). The entire reason I want to have the array inlined in the object is to reduce the number of allocations.

This is a situation where I would be willing to use a hack, even an ugly one, provided it works. One possible hack I am thinking about would be:

class Knot {
  public:
    struct dummy { char padding[sizeof(Gordian)]; };
    dummy inlined_array[8];
    Gordian* get(int index) {
      return reinterpret_cast<Gordian*>(&inlined_array[index]);
    }
    Knot() {
      for (int x = 0; x != 8; x++) {
        new (get(x)) Gordian(x*x);
      }
    }
};

Sure, it compiles, but I'm not exactly an experienced C++ programmer. That is, I couldn't possibly trust my hacks less. So, the questions:

1) Does the hack I came up with seem workable? What are the issues? (I'm mainly concerned with C++0x on newer versions of GCC).

2) Is there a better way to inline an array of non-default constructible objects in a class?

like image 846
porgarmingduod Avatar asked Dec 22 '25 07:12

porgarmingduod


2 Answers

For one thing, you can use an array wrapper (such as boost::array) to initialize the array with fixed values:

#include <boost/array.hpp>

class Gordian {
public:
    int member;
    Gordian(int must_have_variable) : member(must_have_variable) {}
};

namespace detail
{
    boost::array<Gordian, 8> chop()
    {
        boost::array<Gordian, 8> t = {{0, 1, 4, 9, 16, 25, 36, 49}};
        return t;
    }
}

class Knot {
    boost::array<Gordian, 8> array;
public:
    Knot(): array(detail::chop()) {}
};

Another possibility would be an array of boost::optional (but there will be some size overhead):

#include <boost/optional.hpp>

class Gordian {
public:
    int member;
    Gordian(int must_have_variable) : member(must_have_variable) {}
};

class Knot {
    boost::optional<Gordian> array[8];
public:
    Knot()
    {
        for (int x = 0; x != 8; x++) {
            array[x] = Gordian(x*x);
        }
    }
};
like image 103
UncleBens Avatar answered Dec 23 '25 19:12

UncleBens


Memory alignment could break, since Knot thinks it contains nothing but chars. Other than that the trick is workable. Another more general trick I've seen is to provide insertion member functions that return raw memory to be populated by the caller, e.g.:

SuperOverOptimisedVector<Gordian> v;
...
Gordian* g = v.append();
new (g) Gordian(42);

Appearances can be deceiving, so I'll explain. The v.append() function doesn't allocate raw memory from the heap. It simply finds the next available slot in the vector (resizing and copying if capacity is exhausted, the same way std::vector does) and passes the address of that slot back to the caller.

This trick, while cute and clever, is somewhat bizarre and error-prone. This can be partly mitigated by adhering to a one-step convention:

new (v.append()) Gordian(42);

But I prefer to treat it as an interesting curiosity that should generally be avoided.

So in summary, yes you can store non-default-constructible objects in a contiguous array, but unless the performance difference is large enough to impact the success of your project, just go with std::vector.

like image 21
Marcelo Cantos Avatar answered Dec 23 '25 19:12

Marcelo Cantos