I commonly come across the need to create arrays or vectors of polymorphic objects. I'd usually prefer to use references, rather than smart pointers, to the base class because they tend to be simpler.
Arrays and vectors are forbidden from containing raw references, and so I've tended to use smart pointers to the base classes instead. However, there is also the option to use std::reference_wrapper
instead: https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
From what I can tell from the documentation, this is what one of its intended uses is, but when the topic of arrays containing polymorphic objects comes up, the common advice seems to be to use smart pointers rather than std::reference_wrapper
.
My only thought is that smart pointers may be able to handle the lifetime of the object a little neater?
TL:DR; Why are smart pointers, such as std::unique_ptr
seemingly preferred over std::reference_wrapper
when creating arrays of polymorphic objects?
Polymorphism is a feature that allow multiples data type to behave the same way through a common interface. For instance, Computer // Base class | | Notebook Desktop // Both inherits of Computer. Polymorphism would allow you to manage an array of Computer, no matter if they are a Notebook or a Desktop.
Polymorphism is an important concept of object-oriented programming. It simply means more than one form. That is, the same entity (function or operator) behaves differently in different scenarios. For example, The + operator in C++ is used to perform two specific functions.
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&&... ); // todo
private:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With