12

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

Roddy
  • 66,617
  • 42
  • 165
  • 277
  • 1
    Template could be the answer, tried? – Rakib May 29 '14 at 12:14
  • 1
    I'm thinking in terms of (i) templates (ii) factories and (iii) delegated constructors (C++11). Could be of help. – Bathsheba May 29 '14 at 12:15
  • @Bathsheba, factories will lead to same `if-else` block, though localized. – Rakib May 29 '14 at 12:16
  • 1
    One way is to separate construction from initialization (e.g. `Foo f; if(condition) f.initialize(myInt); else f.initialize(myString);`), but that has its own problems... – dlf May 29 '14 at 12:17
  • @bathsheba I currently use a factory, but of course it has to return a pointer, because the object is noncopyable. Sadly I don't have C++11 yet, but I'm curious if that will help. – Roddy May 29 '14 at 12:17
  • @RakibulHasan Template constructor choice would happen at compile time, not run-time, surely? I admit I haven't pursued that direction... – Roddy May 29 '14 at 12:30
  • @Roddy, everything related to Template deduction happens at compile time. It would be helpful I you post more details, like purpose of the constructor ( each partially initialize the members or deduce other values from passed parameters etc) – Rakib May 29 '14 at 12:34
  • C++11 comes to help with uniform initialisation and lambdas (check my answer). If you don't have access to this subset of C++11, but are on Visual Studio, there is a hacky way, too... – gwiazdorrr May 29 '14 at 13:56

6 Answers6

2

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.

gwiazdorrr
  • 6,181
  • 2
  • 27
  • 36
  • 1
    It might be worth mentioning in the answer that the binding to a mutable reference is a second VS hack, and also not-standard. – Mooing Duck May 29 '14 at 16:23
  • This seems kinda hacky to me - would this Foo be a member of the class? If not then why not just create the instance on demand? I'm not sure what this is gaining? – paulm May 29 '14 at 18:58
  • I don't think you understood the question. That said, the gain here is that once you can switch between constructors *for stack objects*, your code can be more compact, localized and with less repetition. It's still ugly, but may be a viable solution in some cases. – gwiazdorrr May 29 '14 at 20:00
1

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)

  • Sadly `Foo` isn't my design, and the string vs. int thing is just to show the problem. The actual class is Windows `Gdiplus::Bitmap` http://msdn.microsoft.com/en-gb/library/windows/desktop/ms534420(v=vs.85).aspx – Roddy May 29 '14 at 12:28
1

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.

sshannin
  • 2,767
  • 1
  • 22
  • 25
  • Risky but interesting, I hadn't considered that, I admit! – Roddy May 29 '14 at 12:47
  • I don't really think it's risky. You can just make a local object which cleans up (calls the destructor on the pointer) when it goes out of scope. Added a note about `boost::optional` if you're more comfortable with that. – sshannin May 29 '14 at 12:52
0

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();
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • 1
    Not really avoiding the dynamic allocation though, might as well just use a std::unique_ptr directly – paulm May 29 '14 at 18:29
0

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.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • Doesn't work on clang: http://goo.gl/SFY8BT; makes you wonder whether it is standard compliant. – gwiazdorrr May 29 '14 at 15:27
  • @gwiazdorrr Interesting. Compiles fine on gcc 4.9. Need to look into this indeed. – Felix Glas May 29 '14 at 15:37
  • @gwiazdorrr [GCC doesn't seem to use the move ctor at all](http://coliru.stacked-crooked.com/a/fb7326b9615ee3bf) while Clang requires it to *not* be deleted. – Felix Glas May 29 '14 at 15:58
  • If there is a user defined copy constructor or it is deleted, move constructor gets deleted as well. That's the case with `Foo` here, since it derives from boost::noncopyable. Hence, it will not work on clang, because it seems to require move constructor being accessible. – gwiazdorrr May 29 '14 at 16:14
  • Check with [clang](http://coliru.stacked-crooked.com/a/e05428d36663c58b), it does call move constructor. – gwiazdorrr May 29 '14 at 21:29
  • @gwiazdorrr Yes it does, as suspected. As far as I can understand, the standard requires a temporary to be created by the ternary operator if the types of the respective arguments differ. However, in this case they would appear to be the same. It seems Clang creates an unnecessary temporary? – Felix Glas May 29 '14 at 21:36
  • @gwiazdorrr Actually a temporary is *always* created even when both operands have the same type. `5.16/6 — The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.`. **It seems Clang is right**. Although the standard says the temp is copy-initialized, not move-initialized as happens with Clang? – Felix Glas May 29 '14 at 23:05
  • According to standard, copy initialization may invoke a move (8.5/14), so everything is correct here. – gwiazdorrr May 30 '14 at 08:47
0

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(...);
Roddy
  • 66,617
  • 42
  • 165
  • 277