Array of polymorphic objects

Array of polymorphic objects



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


std::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.


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?


std::unique_ptr


std::reference_wrapper






If the objects are owned somewhere else, reference_wrapper is fine. However, if this container owns the objects and controls their lifetime, you must use a smart pointer such as unique_ptr - reference_wrapper does not provide any ownership semantics.

– BoBTFish
Sep 7 '18 at 12:34


reference_wrapper


unique_ptr


reference_wrapper






unique_ptr manages the lifetime of the owned object, reference_wrapper stores a pointer to the object. Use unique_ptrs to guarantee the release of the object!

– Alberto Miola
Sep 7 '18 at 12:34







a reference is just a reference, someone still needs to take care of the lifetime of the objects. With a smartpointer you get all you need at once

– user463035818
Sep 7 '18 at 12:34






actually you already have the answer in your question. The only thing to add is that it is not about "neater", but references dont handle the lifetime at all

– user463035818
Sep 7 '18 at 12:35






std::reference_wrapper as a container element type doesn't offer any advantages over a regular dumb pointer, except that the reference wrapper cannot be null.

– n.m.
Sep 7 '18 at 12:50


std::reference_wrapper




4 Answers
4



In very simple terms:



unique_ptr is the owner of the object. It manages the lifetime of the owned object


unique_ptr



reference_wrapper wraps a pointer to an object in memory. It does NOT manage the lifetime of the wrapped object


reference_wrapper



You should create an array of unique_ptr (or shared_ptr) to guarantee the release of the object when it's not needed anymore.


unique_ptr


shared_ptr






Try to always use smart pointers when you deal with objects that live in the heap. I use pointers like you do when I need to deal with polymorphism but I always try to not use pointers at all!

– Alberto Miola
Sep 7 '18 at 12:55




If you are sufficiently motiviated, you can write a poly_any<Base> type.


poly_any<Base>



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.


poly_any<Base>


any


Base


.base()


Base&



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:


poly_any<Base>


get_base


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.


std::vector<poly_any<Base>>


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.


std::any



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.


reference_wrapper



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.


reference_wrapper


vector<uniq_ptr<>>


vector<shared_ptr<>>



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.


vector<reference_wrapper<T>>


vector<T*>



Let's try the experiment:


#include <iostream>
#include <vector>
#include <memory>
#include <functional>

class Base
public:
Base()
std::cout << "Base::Base()" << std::endl;


virtual ~Base()
std::cout << "Base::~Base()" << std::endl;

;

class Derived: public Base
public:
Derived()
std::cout << "Derived::Derived()" << std::endl;


virtual ~Derived()
std::cout << "Derived::~Derived()" << std::endl;

;

typedef std::vector<std::reference_wrapper<Base> > vector_ref;
typedef std::vector<std::shared_ptr<Base> > vector_shared;
typedef std::vector<std::unique_ptr<Base> > vector_unique;

void fill_ref(vector_ref &v)
Derived d;
v.push_back(d);


void fill_shared(vector_shared &v)
std::shared_ptr<Derived> d=std::make_shared<Derived>();
v.push_back(d);


void fill_unique(vector_unique &v)
std::unique_ptr<Derived> d(new Derived());
v.push_back(std::move(d));


int main(int argc,char **argv)

for(int i=1;i<argc;i++)
if(std::string(argv[i])=="ref")
std::cout << "vector" << std::endl;
vector_ref v;
fill_ref(v);
std::cout << "~vector" << std::endl;
else if (std::string(argv[i])=="shared")
std::cout << "vector" << std::endl;
vector_shared v;
fill_shared(v);
std::cout << "~vector" << std::endl;
else if (std::string(argv[i])=="unique")
std::cout << "vector" << std::endl;
vector_unique v;
fill_unique(v);
std::cout << "~vector" << std::endl;





running with argument shared:


vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()



running with argument unique


vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()



running with argument ref


vector
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
~vector



Explanation:


Derived


d


fill_shared()


Derived


d


fill_ref()


fill_ref()



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Crossroads (UK TV series)

ữḛḳṊẴ ẋ,Ẩṙ,ỹḛẪẠứụỿṞṦ,Ṉẍừ,ứ Ị,Ḵ,ṏ ṇỪḎḰṰọửḊ ṾḨḮữẑỶṑỗḮṣṉẃ Ữẩụ,ṓ,ḹẕḪḫỞṿḭ ỒṱṨẁṋṜ ḅẈ ṉ ứṀḱṑỒḵ,ḏ,ḊḖỹẊ Ẻḷổ,ṥ ẔḲẪụḣể Ṱ ḭỏựẶ Ồ Ṩ,ẂḿṡḾồ ỗṗṡịṞẤḵṽẃ ṸḒẄẘ,ủẞẵṦṟầṓế