Polymorphic objects on the stack?

Looks like polymorphism to me.

Polymorphism in C++ works when you have indirection; that is, either a pointer-to-T or a reference-to-T. Where T is stored is completely irrelevant.

Bjarne also makes the mistake of saying "heap-allocated" which is technically inaccurate.

(Note: this doesn't mean that a universal base class is "good"!)


I think Bjarne means that obj, or more precisely the object it points to, can't easily be stack-based in this code:

int f(int arg) 
{ 
    std::unique_ptr<Base> obj;    
    switch (arg) 
    { 
    case 1:  obj = std::make_unique<Derived1      >(); break; 
    case 2:  obj = std::make_unique<Derived2      >(); break; 
    default: obj = std::make_unique<DerivedDefault>(); break; 
    } 
    return obj->GetValue(); 
}

You can't have an object on the stack which changes its class, or is initially unsure what exact class it belongs to.

(Of course, to be really pedantic, one could allocate the object on the stack by using placement-new on an alloca-allocated space. The fact that there are complicated workarounds is beside the point here, though.)

The following code also doesn't work as might be expected:

int f(int arg) 
{ 
    Base obj = DerivedFactory(arg); // copy (return by value)
    return obj.GetValue();
}

This code contains an object slicing error: The stack space for obj is only as large as an instance of class Base; when DerivedFactory returns an object of a derived class which has some additional members, they will not be copied into obj which renders obj invalid and unusable as a derived object (and quite possibly even unusable as a base object.)

Summing up, there is a class of polymorphic behaviour that cannot be achieved with stack objects in any straightforward way.


Of course any completely constructed derived object, wherever it is stored, can act as a base object, and therefore act polymorphically. This simply follows from the is-a relationship that objects of inherited classes have with their base class.


Having read it I think the point is (especially given the second sentence about copy-semantics) that universal base class is useless for objects handled by value, so it would naturally lead to more handling via reference and thus more memory allocation overhead (think template vector vs. vector of pointers).

So I think he meant that the objects would have to be allocated separately from any structure containing them and that it would have lead to many more allocations on heap. As written, the statement is indeed false.

PS (ad Captain Giraffe's comment): It would indeed be useless to have function

f(object o)

which means that generic function would have to be

f(object &o)

And that would mean the object would have to be polymorphic which in turn means it would have to be allocated separately, which would often mean on heap, though it can be on stack. On the other hand now you have:

template <typename T>
f(T o) // see, no reference

which ends up being more efficient for most cases. This is especially the case of collections, where if all you had was a vector of such base objects (as Java does), you'd have to allocate all the objects separately. Which would be big overhead especially given the poor allocator performance at time C++ was created (Java still has advantage in this because copying garbage collector are more efficient and C++ can't use one).


Bjarne's statement is not correct.

Objects, that is instances of a class, become potentially polymorphic by adding at least one virtual method to their class declaration. Virtual methods add one level of indirection, allowing a call to be redirected to the actual implementation which might not be known to the caller.

For this it does not matter whether the instance is heap- or stack-allocated, as long as it is accessed through a reference or pointer (T& instance or T* instance).

One possible reason why this general assertion slipped onto Bjarne's web page might be that it is nonetheless extremely common to heap-allocate instances with polymorphic behavior. This is mainly because the actual implementation is indeed not known to the caller who obtained it through a factory function of some sort.