1

Coming to C++ with a Java background, I'd like to set up some polymorphic code by initializing a variable of type A with one of two implementations, B or C.

My question is whether there is an easy way to do this on the stack. I have a case where I'm only using A inside the method body, and want it destroyed at the end of the function, so touching the heap is optional.

Here's how I would do it on the heap:

A* a = NULL;
if (p) {
  B* b = new B();
  b->setSomethingImplementationSpecific();
  a = b;
}
else {
  a = new C();
}

doSomething(a);
delete(a);

In practice I'd probably pull this out into a factory method, and use an auto_ptr to avoid the delete(a).

This works, but can I do it on the stack? My thought pattern is something like this:

A* a = NULL;
if (p) {
  B b;
  b.setSomethingImplementationSpecific();
  a = &b;
}
else {
  C c;
  a = &c;
}

doSomething(a);

Now I don't have to bother with delete(a), but doSomething(a) won't work, since the B or C is destroyed when they go out of scope.

I've been trying to figure out a way to do part of it with the ternary operator as well, but I end up both borking up the syntax and taking the address of a temporary -- so am I right that there is no way to do this?

A * const a = &(p ? B() : C());

Advice on whether it's a silly idea to implement polymorphism on the stack in the first place is welcome, but mostly I'm trying to better understand the limits of C/C++ in this area, independently of design sense.

SigmaX
  • 463
  • 6
  • 13

5 Answers5

4

If you are using C++11, you can get "stack semantics" by using a unique_ptr:

std::unique_ptr<A> a = (p ? new B() : new C());

Although the object itself will still be allocated on the heap.

std::auto_ptr<A> is the equivalent idiom in C++03.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • 1
    -1 Re `const A &a = (p ? B() : C());`, while reference binding does work that way, the choice operator doesn't. – Cheers and hth. - Alf Apr 13 '14 at 22:41
  • 1
    that fix takes care of the choice operator's type requirements, but are you sure about lifetimes and UB? – Cheers and hth. - Alf Apr 13 '14 at 22:46
  • @Cheersandhth.-Alf: Yes, quite sure. Read Q&A number 3 in [Herb Sutter's old article](http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/). Do I need to cite the spec myself to get you to remove your downvote? – Nemo Apr 13 '14 at 22:56
  • 1
    Well that's what I meant when I wrote "reference binding works that way". However, the choice operator does not work that way. If you do this for a function actual argument then you're technically on safe ground, because those temporaries last until the end of the full expression. A temporary in a sub-expression of an initializer also has that lifetime. Which is a bit too short. – Cheers and hth. - Alf Apr 13 '14 at 23:00
  • Yeah, I cannot do what I was trying to do without slicing the object. I believe the behavior is defined -- the relevant temporary's life cannot end until the containing "full expression" completes, and binding to a const reference extends that -- but the conditional operator + initializer slices the object. – Nemo Apr 13 '14 at 23:30
  • Consider the case where the base class is abstract. Then it cannot be instantiated. Hence no base class temporary to receive a slicy copy. – Cheers and hth. - Alf Apr 13 '14 at 23:37
  • 1
    @Cheersandhth.-Alf: Well, now I am unsure again... Example: http://goo.gl/Y1GI0n Every compiler I have tried does "the right thing" with this example, and does not complain even with `-Wall`. Which does not prove anything, of course, but makes me unsure what the standard requires. I think I shall make this a question. – Nemo Apr 13 '14 at 23:55
3

You can do this cleanly using std::aligned_union for storage:

template <typename...T>
using storage_t = typename std::aligned_union<0, T...>::type;

and a custom unique_ptr deleter:

struct placement_deleter {
  template <typename T>
  void operator () (T* ptr) const {
    ptr->~T();
  }
};

template <typename T>
using stack_ptr = std::unique_ptr<T, placement_deleter>;

Resulting in the usage:

storage_t<B, C> storage;
stack_ptr<A> a;
if (p) {
  auto b = new (&storage) B();
  a.reset(b);
  b->setSomethingImplementationSpecific();
} else {
  a.reset(new (&storage) C());
}

doSomething(*a);

See it live at Coliru.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • This comes closest to doing what I originally imagined cleanly, and avoid the try-catch in Jeff's answer. In practice, though, I went with Nemo's answer, using the heap. – SigmaX Apr 14 '14 at 18:04
  • I would put `a.reset(b);` before the `setSomethingImplementationSpecific();`. If that method throws, we do want `~B()` called. – aschepler Apr 14 '14 at 22:11
2

Instead of this original code,

A* a = NULL;
if (p) {
  B* b = new B();
  b->setSomethingImplementationSpecific();
  a = b;
}
else {
  a = new C();
}

doSomething(a);
delete(a);

you can do this:

void doSomething( A const& ) {}

void doBeeDoo( B&& b )
{
    b.doSomethingImeplementationSpecific();
    doSomething( b );
}

void foo()
{
    if( p ) { doBeeDoo( B() ); } else { doSomething( C() ); }
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
2

You could do something like this with boost::optional:

#include <boost/optional.hpp>

void example(bool p) {
  boost::optional<B> b;
  boost::optional<C> c;
  A* a = nullptr;
  if (p) {
    b = B();
    b->setSomethingImplementationSpecific();
    a = b.get_ptr();
  }
  else {
    c = C();
    a = c.get_ptr();
  }
  doSomething(a);
}

Note that b and c must have a long-enough lifetime. But only one of them calls a constructor and destructor for B or C.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • The implementation of boost::option uses placement new to keep things on the stack. So this is very similar to the accepted answer, but requires allocating memory for both a B and C even though only one or the other is used. – SigmaX Apr 14 '14 at 21:03
1

What you have won't work. b and c will be deleted from the stack by the time you get to doSomething(a);. However, you could do this:

if (p) {
  B b;
  b.setSomethingImplementationSpecific();
  doSomething(&b);
}
else {
  C c;
  doSomething(&c);
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270