1

Suppose I have:

class A { };
class B : public A { };
A f() { 
    if (Sunday())
        return A;
    else
        return B;
}

Obviously this doesn't work since A's copy constructor would be called. Is there anyway to return stack allocated object without losing it's type?

I've tried using std::shared_ptr<A> but it got me into another issue since std::shared_ptr<B> isn't std::shared_ptr<A>.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Shmoopy
  • 5,334
  • 4
  • 36
  • 72
  • Shared pointers should work fine. – Kerrek SB Aug 24 '14 at 18:38
  • You said that you wanted to allocate your object "on the stack". Yet you talk about `shared_ptr`. But `shared_ptr` always manages objects allocated *dynamically*, not "on the stack". So, do you need it to be "on the stack" or not? – AnT stands with Russia Aug 24 '14 at 19:09
  • @AndreyT I'd prefer it to be on the stack if possible (why are you using quotation marks?) but if it's not possible I'll use what the language allows. – Shmoopy Aug 24 '14 at 19:13
  • 1
    @Shmoopy: Because the C++ language has no notion of "on the stack", only storage duration, which can be static (globals, for example), dynamic (`malloc`, `operator new`), and automatic. For most platforms, automatic variables will go on the stack as an implementation detail.. – Ben Voigt Aug 24 '14 at 19:22

3 Answers3

2

It's not possible because of slicing. Use std::unique_ptr instead. You won't lose the dynamic type, but it will be accessible only through the interface of A.

Community
  • 1
  • 1
erenon
  • 18,838
  • 2
  • 61
  • 93
  • Careful with `std::unique_ptr` to make sure the object is correctly destroyed after an upcast -- safest way to do that is by adding a virtual destructor. `shared_ptr` doesn't need this, since it stores a deleter which knows the real type. – Ben Voigt Aug 24 '14 at 19:26
2

It is not immediately possible to return a stack-allocated (i.e. local) object out of the function that created that object. Local objects are destroyed on function return. You can hide/obfuscate the actual nature of the object's allocation by using various "smart pointers" and similar techniques, but the object itself should be allocated dynamically.

Other that that, as long as the local object lifetime rules are obeyed, polymorphism for local objects works in exactly the same way as it works for any other objects. Just use a pointer or a reference

A a;
B b;

A *p = Sunday() ? &a : &b;
// Here `*p` is a polymorphic object

Pointer p in the above example remains valid as long as the local object lives, which means that you cannot return p from a function.

Also, as you see in the example above, it unconditionally creates both objects in advance, and then chooses one of the two while leaving the second one unused. This is not very elegant. You cannot create different versions of such object in different branches of if statement for the very same reasons for which you cannot return a local object from a function polymorphically: once the local block that created the object is complete, the object is destroyed.

The latter problem can be worked around by using a raw buffer and manual in-place construction

alignas(A) alignas(B) char object_buffer[1024]; 
// Assume it's big enough for A and B

A *p = Sunday() ? new(buffer) A() : new (buffer) B();
// Here `*p` is a polymorphic object
p->~A(); // Virtual destructor is required here

but it does not look pretty. A similar technique (involving copying of the buffer) can probably be used to make local polymorphic objects survive block boundaries (see @Dietmar Kühl's answer).

So, again, if you want to create only one object of the two and have your object to survive block boundaries, then immediate solutions put local objects are out of the question. You will have to use dynamically allocated objects.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
0

The easiest approach is certainly to use a suitable smart pointer, e.g., std::unique_ptr<A>, as the return type and to allocate the object on the heap:

std::unique_ptr<A> f() {
    return std::unique_ptr<A>(Sunday()? new B: new A);
}

For the approach returning a std::unique_ptr<A> which may point to a B, it is necessary that A has a virtual destructor as otherwise the code may result in undefined behavior when the std::unique_ptr<A> actually points to a B object. If A doesn't have a virtual destructor and can't be changed, the problem can be avoided by using a suitable std::shared_ptr<...> or by using a suitable deleter with the std::unique_ptr<...>:

std::unique_ptr<A, void(*)(A*)> f() {
    if (Sunday()) {
        return std::unique_ptr<A, void(*)(A*)>(new B, [](A* ptr){ delete static_cast<B*>(ptr); });
    }
    else {
        return std::unique_ptr<A, void(*)(A*)>(new A, [](A* ptr){ delete ptr; });
    }
}

If you don't want to allocate the objects on the heap, you can use a holder type which stores a union with A and B which is then appropriately constructed and destructed (the code below assumes that the copy of A or B won't throw an exception; if necessary, suitable move construction and move assignment can be added):

class holder {
    bool is_b;
    union {
        A a;
        B b;
    } element;
public:
    holder(): is_b(Sunday()) {
        if (this->is_b) {
            new(&this->element.b) B();
        }
        else {
            new(&this->element.a) A();
        }
    }
    holder(holder const& other) { this->copy(other); }
    void copy(holder const& other) {
        this->is_b = other.is_b;
        if (this->is_b) {
            new(&this->element.b) B(other.element.b);
        }
        else {
            new(&this->element.a) A(other.element.a);
        }
    }
    ~holder() { this->destroy(); }
    void destroy() {
        if (this->is_b) {
            this->element.b.~B();
        }
        else {
            this->element.a.~A();
        }
    }
    holder& operator= (holder const& other) {
        this->destroy();
        this->copy(other);
        return *this;
    }
    operator A const&() const { return this->is_b? this->element.b: this->element.a; }                
    operator A&()             { return this->is_b? this->element.b: this->element.a; }
};
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • The `unique_ptr` version causes undefined behavior, because it will later delete a non-polymorphic object using a base type, correct? – Ben Voigt Aug 24 '14 at 19:20
  • @BenVoigt: correct: there needs to be a `virtual` destructor in `A` for this approach to work. However, given that a polymorphic `A` is only useful if there actually `virtual` functions I assume that the example given in the original post was an abbreviated version of `A` and it should, at minimum, have read `class A { public: virtual ~A(); };` – Dietmar Kühl Aug 24 '14 at 19:26
  • And probably the assignment operator should be customized or else deleted. Or is it deleted by default? – Ben Voigt Aug 24 '14 at 19:28
  • @BenVoigt: it seems the assignment operator isn't deleted! That raises the interesting question what the compiler actually does upon assignment - I don't know what it does. – Dietmar Kühl Aug 24 '14 at 19:30