Choosing constructor for a noncopyable object

883 Views Asked by At

Assume I have a non-copyable class with multiple constructors with like this

class Foo: boost::noncopyable
{
  public:
    Foo(std::string s) {...};  // construct one way        
    Foo(int i) {...};  // construct another way
 }

Now, I want to construct an object, and choose which constructor to use at runtime:

I could do it with pointers like this:-

boost::shared_ptr<Foo> f;

if (condition)
  f.reset(new Foo(myString));
else
  f.reset(new Foo(myInteger));

// common code follows
f->doSomethingComplicated(...);

But that feels messy and slow. Is there an easy way to choose the constructor for the object without resorting to dynamic allocation?


Some more details : The Foo class above is simply to illustrate the problem.The actual class involved is Windows Gdiplus::Bitmap - http://msdn.microsoft.com/en-gb/library/windows/desktop/ms534420(v=vs.85).aspx

6

There are 6 best solutions below

8
On

Using C++11 you can do:

Foo&& f = condition ? Foo{myInteger} : Foo{myString};
f.doSomethingComplicated();

By using an r-value reference you avoid the constness and still extend the lifetime of the temporary.

1
On

You have a flaw in your design. The 'Foo' is delegating the type elision to the user of the class. Have a look at boost::any (http://www.boost.org/doc/libs/1_55_0/doc/html/any.html)

2
On

I think the best option that meets your requirements (not dynamically allocated, not-copyable, pre-C++11) would be to use the placement new.

See here for a short example.

boost::optional might also be an acceptable option for you (it basically just does this for you internally, as well as keeping track of whether the object has been initialized). The in-place construction is a bit messy though for optional imo.

1
On

Keep it small and simple, I'd say. If this is a very local problem, why not just repeat the call?

if (condition)
{
  Foo f(myString);
  f.doSomethingComplicated();
}
else
{
  Foo f(myInt);
  f.doSomethingComplicated();
}

If that does not prove feasible, wrap a Foo pointer (or smart pointer) in a new class.

class FooWrapper // copyable
{
private:
    boost::shared_ptr<Foo> m_foo;
public:
    FooWrapper(std::string const &s) : m_foo(new Foo(s)) {}
    FooWrapper(int i) : m_foo(new Foo(i)) {}
    void doSomethingComplicated() { m_foo->doSomethingComplicated(); }
};

FooWrapper foo = condition ? FooWrapper(myString) : FooWrapper(myInt);
foo.doSomethingComplicated();
3
On

You can do it with C++11 on stack, without placement new and without copy/move constructor/assignment available. Observe:

auto factory = [&]() -> Foo 
{ 
  if (condition) {
    return { myString };
  } else {
    return { myInteger };
  }
};
Foo&& foo = factory();
foo.doSomethingComplicated();

The functor and the Foo instance will live happily on stack, no allocations made (except for copying string in Foo's constructor, probably). Foo will get its destructor called when it gets out of scope. Win.

When using uniform initialization to construct return value, no copy/move operation is involved. Note that returning myString (implicit conversion) or Foo(myString) would force compiler to check if object is copyable/moveable, even if such copy/move could be elided.

Edit: alternatively, this can be made even shorter, but bit more "magical":

Foo&& foo = [&]() -> Foo 
{ 
  if (condition) {
    return { myString };
  } else {
    return { myInteger };
  }
}();

Edit: Pre-C++11, Visual Studio hacky solution:

VC doesn't seem to check whether object is copyable when resorting on implicit conversion constructors to return value from a function. So this is valid, even though it's against the standard:

Foo make_foo(bool condition, const std::string&s, int i)
{
    if ( condition) {
        return s;
    } else {
        return i;
    }
}

Then just use it like that:

Foo& f = make_foo(condition, myString, myInteger);

Note that this is yet another VC hack, as according to standard temporary can't be assigned to a reference to a mutable object, i.e. it would need to be changed const Foo&, which would be quite limiting here.

Not saying that you should handle it this way, but it is possible.

0
On

If you only need to construct a temporary object, I think you can bind it to a const reference using a ternary expression:

const Foo &f(condition ? Foo(myString) : Foo(myInteger));

f.doSomethingComplicated(...);