C++ style virtual method based polymorphism:
- You have to use classes to hold your data.
- Every class has to be built with your particular kind of polymorphism in mind.
- Every class has a common binary-level dependency, which restricts how the compiler creates the instance of each class.
- The data you are abstracting must explicitly describe an interface that describes your needs.
C++ style template based type erasure (with virtual method based polymorphism doing the erasure):
- You have to use template to talk about your data.
- Each chunk of data you are working on may be completely unrelated to other options.
- The type erasure work is done within public header files, which bloats compile time.
- Each type erased has its own template instantiated, which can bloat binary size.
- The data you are abstracting need not be written as being directly dependent on your needs.
Now, which is better? Well, that depends if the above things are good or bad in your particular situation.
As an explicit example, std::function<...>
uses type erasure which allows it to take function pointers, function references, output of a whole pile of template-based functions that generate types at compile time, myraids of functors which have an operator(), and lambdas. All of these types are unrelated to one another. And because they aren't tied to having a virtual operator()
, when they are used outside of the std::function
context the abstraction they represent can be compiled away. You couldn't do this without type erasure, and you probably wouldn't want to.
On the other hand, just because a class has a method called DoFoo
, doesn't mean that they all do the same thing. With polymorphism, it isn't just any DoFoo
you are calling, but the DoFoo
from a particular interface.
As for your sample code... your GetSomeText
should be virtual ... override
in the polymorphism case.
There is no need to reference count just because you are using type erasure. There is no need not to use reference counting just because you are using polymorphsm.
Your Object
could wrap T*
s like how you stored vector
s of raw pointers in the other case, with manual destruction of their contents (equivalent to having to call delete). Your Object
could wrap a std::shared_ptr<T>
, and in the other case you could have vector
of std::shared_ptr<T>
. Your Object
could contain a std::unique_ptr<T>
, equivalent to having a vector of std::unique_ptr<T>
in the other case. Your Object
's ObjectModel
could extract copy constructors and assignment operators from the T
and expose them to Object
, allowing full-on value semantics for your Object
, which corresponds to the a vector
of T
in your polymorphism case.