Массив полиморфных объектов
Я обычно сталкиваюсь с необходимостью создания массивов или векторов полиморфных объектов. Обычно я предпочитаю использовать ссылки, а не умные указатели на базовый класс, потому что они, как правило, проще.
массивам и векторам запрещено содержать необработанные ссылки, поэтому я предпочитаю использовать интеллектуальные указатели на базовые классы. Тем не менее, есть также возможность использовать : https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
из того, что я могу сказать из документации, это то, что является одним из его предполагаемых применений, но когда возникает тема массивов, содержащих полиморфные объекты, общим советом, похоже, является использование интеллектуальных указателей, а не std::reference_wrapper
.
моя единственная мысль заключается в том, что умные указатели могут обрабатывать время жизни объекта немного аккуратнее?
TL:DR; почему умные указатели, такие как std::unique_ptr
казалось бы, предпочтительнее std::reference_wrapper
при создании массивов полиморфных объектов?
4 ответов
в очень простых терминах:
unique_ptr
является владельцем объекта. Он управляет временем жизни объекта, принадлежащегоreference_wrapper
упаковывает указатель на объект в памяти. Это не управление временем жизни обернутого объекта
вы должны создать массив unique_ptr
(или shared_ptr
), чтобы обеспечить освобождение объекта, когда он больше не нужен.
если вы не достаточно motiviated, вы можете написать poly_any<Base>
тип.
A 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
функции указателя:
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
обычно использует оптимизацию малого буфера для хранения небольших объектов внутри и больших объектов в куче. Но это деталь реализации.
в основном, a reference_wrapper
является изменяемой ссылкой: Как ссылка, она не должна быть нулевой; но как указатель, вы можете назначить ей в течение ее жизни, чтобы указать на другой объект.
однако, как и указатели и ссылки,reference_wrapper
не управляет временем жизни объекта. Вот что мы используем vector<uniq_ptr<>>
и vector<shared_ptr<>>
for: чтобы убедиться,что указанные объекты правильно удалены.
С точки зрения производительности, vector<reference_wrapper<T>>
должно быть просто как быстро и эффективно памяти, как vector<T*>
. Но оба этих указателя / ссылки могут стать висячими, поскольку они не управляют временем жизни объекта.
давайте попробуем эксперимент:
#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;
}
}
}
запуск с аргументом shared:
vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()
работает с аргументом unique
vector
Base::Base()
Derived::Derived()
~vector
Derived::~Derived()
Base::~Base()
запуск с аргументом ref
vector
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
~vector
объяснение:
-
shared: память разделяется различными частями кода. В Примере
Derived
объект сначала принадлежитd
локальный var в функцииfill_shared()
и вектором. При выходе область действия объекта функции по-прежнему принадлежит вектору и только когда вектор окончательно уходит, объект удаляется -
уникальный: память принадлежит unique_ptr. В Примере
Derived
объект сначала принадлежитd
местный var. Однако это должно быть двигался в вектор, передавая право собственности. Как и раньше, когда единственный владелец уходит, объект удаляется. - ref: нет владение семантикой. Объект создается как локальная переменная