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

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

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