rkjnsn
rkjnsn

Reputation: 985

More control over construction of member objects in C++

I have a couple of classes in which I need more control over member construction than is provided through initialization lists.

In one class, I need the ability to retry the construction of a member with different arguments if the original construction throws.

In a second class, I need to pass the address of the member object to another function which will in turn call the constructor (via placement new). The problem here is that the function is a black box and I don't know what it will pass to the constructor.

In both cases, it is a requirement that the member object is held in the containing object, not dynamically allocated.

What is the best way to accomplish this?

Edit

I noticed the question "Is it possible to defer member initialization to the constructor body?", which recommended boost::optional. This didn't quite solve my problem for a couple of reasons. First, I don't want to delay initialization, just have more control over it. Second, boost::optional stores an extra bool to indicate whether the object is initialized, which is not needed in my case. It did, however, get me thinking, and I came up with the solution I have posted below.

Upvotes: 3

Views: 293

Answers (3)

rkjnsn
rkjnsn

Reputation: 985

I ended up creating the following class:

#include <type_traits> // Or <boost/type_traits.hpp>
template <typename Ty>
class manually_constructed
{
public:
   template <typename T>
   manually_constructed(T construct_func) {
      construct_func(static_cast<Ty*>(static_cast<void*>(&data)));
   }
   ~manually_constructed() {
      static_cast<Ty*>(static_cast<void*>(&data))->~Ty();
   }
   Ty& operator*() {
      return *static_cast<Ty*>(static_cast<void*>(&data));
   }
   Ty* operator->() {
      return static_cast<Ty*>(static_cast<void*>(&data));
   }
private:
   // Replace 'std' with 'boost' if your standard library doesn't support
   // type_traits, yet.
   std::aligned_storage<sizeof(Ty), std::alignment_of<Ty>::value>::type data;
};

Then, in my class, I can have a member such as manually_constructed<OtherClass> object and a private static method such as:

static void construct_object(OtherClass *p) {
   try {
      new(p) OtherClass(/* Risky arguments */);
   } catch(...) {
      new(p) OtherClass(/* Fallback arguments */);
   }
}

Finally, in my initializer list, I can have object(construct_object).

With this solution, all member objects are stored within the containing object, there is no additional space overhead, all member objects are constructed in the correct order, and manually constructed objects will be destructed automatically.

Upvotes: 0

John Humphreys
John Humphreys

Reputation: 39254

You cannot "retry" construction via a constructor alone. You can swallow/handle an exception caused within your constructor using try/catch, and continue to populate the object if code inside the constructor fails though (using simple assignments).

If your initialization list throws, you're... screwed? though - nothing in your initialization list should throw. It's executed prior to your constructor body and you have no control over exceptions thrown in it.

If you really need to retry construction with different parameters, wrap your construction in a factory function which can catch exceptions and try different options when they are received.

Moving on... when you call a constructor, you call a constructor. The constructor itself creates the object on the heap or on the stack based on whether or not you used dynamic allocation mechanisms (mainly new). You cannot decide within a constructor body itself to declare the object somewhere else, the decision has already been made. You'll have to directly use placement new when you call new in order to achieve that.

Upvotes: 4

Dmitriy Kachko
Dmitriy Kachko

Reputation: 2914

Put apart construction and initialization procedure. I know it can sounds odd to you, but make construction of basic object in the initialization list and then try/catch to initialize it in the body of constructor with different sets of parameters with the separated init() method.

Upvotes: 0

Related Questions