Массив полиморфных объектов

Я обычно сталкиваюсь с необходимостью создания массивов или векторов полиморфных объектов. Обычно я предпочитаю использовать ссылки, а не интеллектуальные указатели, базовому классу, потому что они, как правило, проще.

Массивы и векторы запрещены для хранения исходных ссылок, поэтому я обычно использовал интеллектуальные указатели для базовых классов. Тем не менее, есть также возможность использовать 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(), объект уходит, оставляя вектор, указывающий на нераспределенную память.