Array of polymorphic objects
In very simple terms:
unique_ptr
is the owner of the object. It manages the lifetime of the owned objectreference_wrapper
wraps a pointer to an object in memory. It does NOT manage the lifetime of the wrapped object
You should create an array of unique_ptr
(or shared_ptr
) to guarantee the release of the object when it's not needed anymore.
If you are sufficiently motiviated, you can write a poly_any<Base>
type.
A poly_any<Base>
is an any
restricted to only storing objects that derive from Base
, and provides a .base()
method that returns a Base&
to the underlying object.
A very incomplete sketch:
template<class Base>struct poly_any:private std::any{ using std::any::reset; using std::any::has_value; using std::any::type; poly_any( poly_any const& ) = default; poly_any& operator=( poly_any const& ) = default; Base& base() { return get_base(*this); } Base const& base() const { return const_cast<Base const&>(get_base(const_cast<poly_any&>(*this))); } template< class ValueType, std::enable_if_t< /* todo */, bool > =true > poly_any( ValueType&& value ); // todo // TODO: sfinae on ValueType? template< class ValueType, class... Args > explicit poly_any( std::in_place_type_t<ValueType>, Args&&... args ); // todo // TODO: sfinae on ValueType? template< class ValueType, class U, class... Args > explicit poly_any( std::in_place_type_t<ValueType>, std::initializer_list<U> il, Args&&... args ); // todo void swap( poly_any& other ) { static_cast<std::any&>(*this).swap(other); std::swap( get_base, other.get_base ); } poly_any( poly_any&& o ); // todo poly_any& operator=( poly_any&& o ); // todo template<class ValueType, class...Ts> std::decay_t<ValueType>& emplace( Ts&&... ); // todo template<class ValueType, class U, class...Ts> std::decay_t<ValueType>& emplace( std::initializer_list<U>, Ts&&... ); // todoprivate: using to_base = Base&(*)(std::any&); to_base get_base = 0;};
Then you just have to intercept every means of putting stuff into the poly_any<Base>
and store a get_base
function pointer:
template<class Base, class Derived>auto any_to_base = +[](std::any& in)->Base& { return std::any_cast<Derived&>(in);};
Once you have done this, you can create a std::vector<poly_any<Base>>
and it is a vector of value types that are polymorphically descended from Base
.
Note that std::any
usually uses the small buffer optimization to store small objects internally, and larger objects on the heap. But that is an implementation detail.
Basically, a reference_wrapper
is a mutable reference: Like a reference, it must not be null; but like a pointer, you can assign to it during its lifetime to point to another object.
However, like both pointers and references, reference_wrapper
does not manage the lifetime of the object. That's what we use vector<uniq_ptr<>>
and vector<shared_ptr<>>
for: To ensure that the referenced objects are properly disposed off.
From a performance perspective, vector<reference_wrapper<T>>
should be just as fast and memory efficient as vector<T*>
. But both of these pointers/references may become dangling as they are not managing object lifetime.