[c++] Новые и удалить все еще полезны в C ++ 14?



1 Answers

Это относительно распространенный выбор - использовать make_unique и make_shared а не сырые вызовы для new . Однако это не обязательно. Предполагая, что вы решили следовать этому соглашению, есть несколько мест для использования new .

Во-первых, нестандартное размещение new (я буду пренебрегать «нестандартной» частью и просто называть его new местом) - это совершенно другая карточная игра, чем стандартная (без размещения) new . Он логически сопряжен с ручным вызовом деструктора. Стандартный new как приобретает ресурс из бесплатного хранилища, так и создает в нем объект. Он сопряжен с delete , который уничтожает объект и перерабатывает хранилище в свободный магазин. В некотором смысле, стандартное new размещение вызовов new внутренне, а стандартное delete вызывает деструктор внутри.

Размещение new - это то, как вы напрямую вызываете конструктор на каком-либо хранилище, и требуется для расширенного кода управления жизненным циклом. Если вы выполняете optional , безопасное union типа в выровненном хранилище или интеллектуальный указатель (с унифицированным хранилищем и не унифицированным временем жизни, например make_shared ), вы будете использовать new размещение. Затем в конце жизни конкретного объекта вы вызываете его деструктор. Подобно немедленному размещению и delete , размещение new и ручных вызовов деструктора происходит парами.

Нестандартное размещение new является еще одной причиной использования new . Пользовательское размещение new может использоваться для выделения ресурсов из неглобального пула - выделенного выделения или распределения на страницу общей памяти с совместным процессом, выделения в общую память видеокарты и т. Д. - и других целей. Если вы хотите написать make_unique_from_custom который выделяет свою память с помощью специального места размещения new, вам нужно будет использовать new ключевое слово. Пользовательское размещение new может действовать как новое размещение (тем, что оно фактически не получает ресурсы, а скорее каким-то ресурсом каким-то образом), или оно может действовать как стандартное new (при этом оно приобретает ресурсы, возможно, используя переданные аргументы) ,

Выделение пользовательского места размещения вызывается, если выбрано new настраиваемое размещение, поэтому вам может потребоваться его написать. В C ++ вы не вызываете произвольное delete места размещения, оно (C ++) вызывает вас (r перегрузка) .

Наконец, make_shared и make_unique являются неполными функциями, поскольку они не поддерживают пользовательские удалители.

Если вы пишете make_unique_with_deleter , вы все равно можете использовать make_unique для распределения данных, а также .release() в вашу помощь с помощью уникального пользователя. Если ваш делектор хочет ввести свое состояние в буфер с указателем, а не в unique_ptr или в отдельное выделение, вам нужно будет использовать здесь место размещения.

Для make_shared клиентский код не имеет доступа к коду создания «подсчета ссылок». Насколько я могу судить, вы не можете легко иметь «комбинированное распределение объекта и счетного счетного блока» и пользовательский дебетер.

Кроме того, make_shared приводит к тому, что распределение ресурсов (хранилище) для самого объекта сохраняется до тех пор, weak_ptr оно не сохраняется: в некоторых случаях это может быть нежелательно, поэтому вы хотите сделать shared_ptr<T>(new T(...)) чтобы этого избежать.

В тех немногих случаях, когда вы хотите вызывать не-размещение new , вы можете вызвать make_unique , затем .release() указатель, если вы хотите управлять отдельно от этого unique_ptr . Это увеличивает ваш охват RAII ресурсами и означает, что если есть исключения или другие логические ошибки, вы менее склонны к утечке.

Я отметил выше, я не знал, как использовать пользовательский удалён с общим указателем, который легко использует один блок распределения. Вот эскиз того, как это сделать сложно:

template<class T, class D>
struct custom_delete {
  std::tuple<
    std::aligned_storage< sizeof(T), alignof(T) >,
    D,
    bool
  > data;
  bool bCreated() const { return std::get<2>(data); }
  void markAsCreated() { std::get<2>()=true; }
  D&& d()&& { return std::get<1>(std::move(data)); }
  void* buff() { return &std::get<0>(data); }
  T* t() { return static_cast<T*>(static_cast<void*>(buff())); }
  template<class...Ts>
  explicit custom_delete(Ts...&&ts):data(
    {},D(std::forward<Ts>(ts)...),false
  ){}
  custom_delete(custom_delete&&)=default;
  ~custom_delete() {
    if (bCreated())
      std::move(*this).d()(t());
  }
};

template<class T, class D, class...Ts, class dD=std::decay_t<D>>
std::shared_ptr<T> make_shared_with_deleter(
  D&& d,
  Ts&&... ts
) {
  auto internal = std::make_shared<custom_delete<T, dD>>(std::forward<D>(d));
  if (!internal) return {};
  T* r = new(internal->data.buff()) T(std::forward<Ts>(ts...));
  internal->markAsCreated();
  return { internal, r };
}

Я думаю, что это нужно. Я попытался разрешить безгосударственным удалениям использовать пространство без использования tuple , но я, возможно, прищурился.

В решении с качеством библиотеки, если T::T(Ts...) является noexcept , я мог бы удалить bCreated служебные данные, так как не было бы возможности для того, чтобы custom_delete был уничтожен до того, как T будет построено.

Question

Учитывая наличие make_unique и make_shared , а также автоматическое удаление make_unique make_shared и shared_ptr , каковы ситуации (помимо поддержки устаревшего кода) для использования new и delete в C ++ 14?




Я бы сказал, что единственной причиной для new и delete является реализация других видов интеллектуальных указателей.

Например, библиотека по-прежнему не имеет навязчивых указателей, таких как boost :: intrusive_ptr, что очень жаль, поскольку они превосходят по производительности соображения общего доступа, как указывает Андрей Александреску.




Related