Как правильно использовать распределители для полиморфных типов?

Я добавляю настраиваемые распределители памяти в систему и пытаюсь сделать это правильно с помощью концепции Allocator, но столкнулся с некоторой неопределенностью, когда достиг точки, когда тип выделенного компонента был стерт.

class Pimpl_interface {
public:
  virtual ~Pimpl_interface() {}
  virtual void foo() = 0;
};

template<typename T>
class Pimpl : public Pimpl_interface {
public:
  void foo() override {}
};

template<typename Alloc = std::allocator<void>>
class MyType : public Alloc {
  Pimpl_interface * pimpl_ = nullptr;
public:  
  MyType(const Alloc& alloc) : Alloc(alloc) {}

  ~MyType() {
    if(pimpl_) {
      pimpl_->~Pimpl_interface();
      // What do I put here ?????
    }
  }

  template<typename T>
  void bar() {
    using real_alloc_t = typename Alloc::template rebind<Pimpl<T>>::other;
    real_alloc_t alloc(*static_cast<Alloc*>(this));
    Pimpl<T>* ptr = alloc.allocate(1);
    try {
      pimpl_ = new(&ptr) Pimpl<T>();
    }
    catch(...) {
      alloc.deallocate(ptr, 1);
      throw;
    }
  }
};

На первый взгляд кажется, что нужно перепривязать Alloc к Pimpl_interface и надеяться, что это сделает правильно. Я знаю, что это будет нормально работать с std::allocator, но предполагается ли, что это то, что пользовательские распределители должны поддерживать для всех перепривязываемых типов распределителей?

В худшем случае я могу вместо этого взять malloc/free-подобный объект-распределитель с утиным типом, но я лучше постараюсь следовать стандартной библиотеке, если смогу.


person Frank    schedule 17.06.2019    source источник
comment
Примечание. Реализации std::shared_ptr по существу должны хранить объект распределителя вместе с блоком управления с подсчетом ссылок, а не непосредственно в shared_ptr...   -  person aschepler    schedule 17.06.2019


Ответы (1)


Я чувствую, что многое происходит. Похоже, что в основе вещей вы хотите безопасно использовать альтернативные распределители (в частности, в классе PImpl'd). Для меня наименьшая полезная часть этого требования — заставить аллокатор работать с std::unique_ptr. Если я правильно понял, думаю, вы захотите, чтобы это был std::unique_ptr<T, std::function<void(T*)>>. Точно так же похоже, что std::allocate_shared может быть достаточно близко к тому, что вы хотите. https://en.cppreference.com/w/cpp/memory/shared_ptr/allocate_shared

Это то, что тебе надо?:

template<typename Alloc = std::allocator<void>>
class MyType {
  Alloc alloc_;
  std::shared_ptr<Pimpl_interface> pimpl_ = nullptr;
public:  
  MyType(const Alloc& alloc) : Alloc(alloc) {}

  ~MyType() {} // shared_ptr ftw.

  template<typename T>
  void bar() {
    using real_alloc_t = typename Alloc::template rebind<Pimpl<T>>::other;
    real_alloc_t alloc(alloc_);
    pimpl_ = std::allocate_shared<Pimpl<T>>(alloc);
  }
};

(Я отказался от наследства от Alloc, потому что это казалось рискованным.)

person Ben    schedule 18.06.2019
comment
Вместо того, чтобы использовать std::function, почему бы не изучить что-нибудь с помощью std::pmr::polymorphic_allocator. - person Deduplicator; 18.06.2019
comment
Ну, мой пример — MCVE, я намеренно не использовал unique_ptr, потому что хотел изолировать вопрос о семантике распределителя отдельно от того, как они взаимодействуют с остальной частью стандартной библиотеки. - person Frank; 18.06.2019
comment
И наследование заключается в использовании оптимизации пустой базы, что характерно и важно для распределителей, поскольку они часто не имеют состояния. - person Frank; 18.06.2019
comment
Наконец, нет, это не отвечает на мой вопрос. Он просто пинает банку на пути к shared_ptr. - person Frank; 18.06.2019
comment
Я не эксперт по аллокаторам, но я думаю, что std::allocate_shared автоматически заполняет d'tor, поэтому, хотя это и не прямой ответ, я думаю, что это устраняет ваш вопрос Что я здесь помещаю ????? - person Ben; 18.06.2019
comment
Примечание: вам нужно быть осторожным, используя std::function в качестве удаления, так как конструкция перемещения/назначение для function может вызывать исключения. (С++ 20 сделает function конструкцию перемещения noexcept, но я не смог получить то же самое для назначения перемещения). - person Nevin; 18.06.2019