views:

135

answers:

3

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?

+1  A: 

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);
        }
    }
};
UncleBens
Can anyone tell my why this answer was downvoted? To me it seems like an interesting possibility. Is there anything wrong with it?
Actually, using boost optional will probably be the easiest approach. A few bytes overhead is not a problem, especially when it saves me from calling the shared memory allocator twice.
+1  A: 

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.

Marcelo Cantos
+1  A: 

Based on the answer I got, and my initial hack, I came up with this general solution using boost::aligned_storage. Basically a void type, but for structures.

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

template <class T>
struct VoidWrap {
  boost::aligned_storage<sizeof(T)> storage;
  T* address() { return reinterpret_cast<T*>(storage.address()); }
};

class Knot {
  public:
    VoidWrap<Gordian> void_gordian[8];
    Knot() {
      for (int x = 0; x != 8; x++) {
        new (void_gordian[x].address()) Gordian(x*x);
      }
    }
};

Or, an expanded version specifically for the use case of 1) Initializing in the constructur of the object containing it. 2) Accessing. 3) Possibly re-assigning the value. 4) Proper automatic destruction. (Of course, it can blow up if it is destroyed/accessed without being initialized)

template <class T>
struct VoidWrap {
  boost::aligned_storage<sizeof(T)> storage;
  /// Returns an address on which to construct the object. Use only once.
  T* construct() { return access(); }
  /// access should only be called on a `construct`ed object
  /// obj.access() is equivalent to &obj
  T* access() { return reinterpret_cast<T*>(this); }
  /// Assumes the object has been constructed. Calls the destructor on the old
  /// object, then returns an address on which to construct a new object.
  T* reconstruct() {
    access()->~T();
    return access(); 
  }
  ~VoidWrap() {
    access()->~T();
  }
};

VoidWrap<Gordian> void_g;
new (void_g.construct()) Gordian(10);
cout << void_g.access()->member << endl;
new (void_g.reconstruct()) Gordian(20);