Массив полиморфных объектов
Я обычно сталкиваюсь с необходимостью создания массивов или векторов полиморфных объектов. Обычно я предпочитаю использовать ссылки, а не интеллектуальные указатели, базовому классу, потому что они, как правило, проще.
Массивы и векторы запрещены для хранения исходных ссылок, поэтому я обычно использовал интеллектуальные указатели для базовых классов. Тем не менее, есть также возможность использовать std::reference_wrapper
: https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
Из того, что я могу сказать из документации, это то, что одно из его предназначенных видов использования, но когда возникает тема массивов, содержащих полиморфные объекты, общий совет, похоже, заключается в использовании интеллектуальных указателей, а не std::reference_wrapper
.
Моя единственная мысль заключается в том, что умные указатели могут обрабатывать время жизни объекта немного аккуратно?
TL: DR; Почему интеллектуальные указатели, такие как std::unique_ptr
кажутся предпочтительными для std::reference_wrapper
при создании массивов полиморфных объектов?
Ответы
Ответ 1
В очень простых терминах:
-
unique_ptr
является владельцем объекта. Он управляет временем жизни принадлежащего ему объекта
-
reference_wrapper
обертывает указатель на объект в памяти. Он не управляет временем жизни упакованного объекта
Вы должны создать массив unique_ptr
(или shared_ptr
), чтобы гарантировать освобождение объекта, когда он больше не понадобится.
Ответ 2
Если вы достаточно мотивированы, вы можете написать poly_any<Base>
.
poly_any<Base>
представляет собой any
ограничиваются только хранением предметов, которые вытекают из Base
, и обеспечивает .base()
метод, который возвращает Base&
к базовому объекту.
Очень неполный эскиз:
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;
};
Затем вам просто нужно перехватить все средства ввода материала в poly_any<Base>
и сохранить get_base
функции get_base
:
template<class Base, class Derived>
auto any_to_base = +[](std::any& in)->Base& {
return std::any_cast<Derived&>(in);
};
Как только вы это сделаете, вы можете создать std::vector<poly_any<Base>>
и это вектор типов значений, которые полиморфно сходят с Base
.
Обратите внимание, что std::any
обычно использует небольшую оптимизацию буфера для хранения небольших объектов внутри и больших объектов в куче. Но это детализация реализации.
Ответ 3
В принципе, reference_wrapper
является изменяемой ссылкой: как ссылка, она не должна быть нулевой; но, как указатель, вы можете назначить ему в течение своей жизни указать другой объект.
Однако, как и указатели, так и ссылки, reference_wrapper
не управляет временем жизни объекта. Это то, что мы используем vector<uniq_ptr<>>
и vector<shared_ptr<>>
для: для обеспечения того, чтобы ссылочные объекты были правильно удалены.
С точки зрения производительности vector<reference_wrapper<T>>
должен быть таким же быстрым и эффективным для памяти, как vector<T*>
. Но оба этих указателя/ссылки могут стать болтливыми, поскольку они не управляют временем жизни объекта.
Ответ 4
Попробуем попробовать:
#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;
}
}
}
работа с общим аргументом:
vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()
работает с уникальным аргументом
vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()
работает с аргументом ref
vector
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
~vector
Объяснение:
- shared: память используется разными частями кода. В этом примере объект
Derived
сначала принадлежит d
local var в функции fill_shared()
и вектором. Когда код выходит из области функционального объекта, все еще принадлежит вектору, и только когда вектор уходит наконец, объект удаляется - unique: Память принадлежит уникальному_ptr. В этом примере объект
Derived
сначала принадлежит d
local var. Однако он должен быть перенесен в вектор, передавая право собственности. Как и раньше, когда единственный владелец уходит, объект удаляется. - ref: Нет семантики владения. Объект создается как локальная переменная функции
fill_ref()
, и ссылка на объект может быть добавлена к вектору. Однако вектор не владеет памятью, а когда код выходит из функции fill_ref()
, объект уходит, оставляя вектор, указывающий на нераспределенную память.