Массив полиморфных объектов - PullRequest
0 голосов
/ 07 сентября 2018

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

Массивам и векторам запрещено содержать необработанные ссылки, и поэтому вместо этого я использовал умные указатели на базовые классы. Однако есть также возможность использовать std::reference_wrapper вместо: https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

Из того, что я могу сказать из документации, это одно из предполагаемых применений, но когда поднимается тема массивов, содержащих полиморфные объекты, обычно советуют использовать умные указатели, а не std::reference_wrapper.

Я думал только о том, что умные указатели могут справиться со временем жизни объекта немного лучше?

TL: DR; Почему умные указатели, такие как std::unique_ptr, по-видимому, предпочтительнее, чем std::reference_wrapper, при создании массивов полиморфных объектов?

Ответы [ 4 ]

0 голосов
/ 07 сентября 2018

Если вы достаточно мотивированы, вы можете написать 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 обычно использует небольшую оптимизацию буфера для внутреннего хранения небольших объектов и больших объектов в куче. Но это деталь реализации.

0 голосов
/ 07 сентября 2018

По сути, reference_wrapper является изменяемой ссылкой: как и ссылка, она не должна быть нулевой;но, как указатель, вы можете назначить ему в течение его времени жизни указатель на другой объект.

Однако, как и указатели, и ссылки, reference_wrapper не управляет временем жизни объекта ,Вот что мы используем vector<uniq_ptr<>> и vector<shared_ptr<>> для: обеспечения правильного удаления объектов, на которые имеются ссылки.

С точки зрения производительности, vector<reference_wrapper<T>> должен быть таким же быстрым и эффективным для памяти, как и vector<T*>,Но оба эти указателя / ссылки могут стать висящими, поскольку они не управляют временем жизни объекта.

0 голосов
/ 07 сентября 2018

Давайте попробуем эксперимент:

#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 в функции fill_shared() и вектору.Когда код выходит из области видимости объекта функции, он все еще принадлежит вектору, и только когда вектор окончательно удаляется, объект удаляется
  • unique : память принадлежит unique_ptr.В этом примере объект Derived сначала принадлежит локальной переменной d.Однако он должен быть перемещен в вектор, передав право собственности.То же, что и раньше, когда единственный владелец уходит, объект удаляется.
  • ref : семантики владения нет.Объект создается как локальная переменная функции fill_ref(), и ссылку на объект можно добавить в вектор.Однако вектор не владеет памятью, и когда код выходит из функции fill_ref(), объект удаляется, оставляя вектор, указывающий на нераспределенную память.
0 голосов
/ 07 сентября 2018

В очень простых сроках:

  • unique_ptr является владельцем объекта. Управляет временем жизни принадлежащего объекта

  • reference_wrapper переносит указатель на объект в памяти. Он НЕ управляет временем жизни обернутого объекта

Вам следует создать массив unique_ptr (или shared_ptr), чтобы гарантировать освобождение объекта, когда он больше не нужен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...