Учитывая наличие make_unique
и make_shared
, а также автоматическое удаление деструкторами unique_ptr
и shared_ptr
, каковы ситуации (помимо поддержки устаревшего кода) для использования new
и delete
в С++ 14?
Новые и удалить все еще полезны в С++ 14?
Ответ 1
В то время как интеллектуальные указатели предпочтительнее для сырых указателей во многих случаях, в С++ 14 есть еще много прецедентов для new
/delete
.
Если вам нужно написать что-нибудь, что требует создания на месте, например:
- пул памяти
- распределитель
- отмеченный вариант
- двоичные сообщения в буфер
вам нужно будет использовать размещение new
и, возможно, delete
. Ничего подобного.
Для некоторых контейнеров, которые вы хотите записать, вы можете использовать необработанные указатели для хранения.
Даже для стандартных интеллектуальных указателей вам все равно потребуется new
, если вы хотите использовать пользовательские удалители, поскольку make_unique
и make_shared
не позволяют этого.
Ответ 2
Общепринято использовать make_unique
и make_shared
вместо необработанных вызовов new
. Однако это не обязательно. Предполагая, что вы решили следовать этому соглашению, есть несколько мест для использования new
.
Во-первых, нестандартное размещение new
(я буду пренебрегать "нестандартной" частью и просто назвать ее местом размещения new
) - это совершенно другая карточная игра, чем стандартная (без размещения) new
, Он логически сопряжен с ручным вызовом деструктора. Стандарт new
как приобретает ресурс из свободного хранилища, так и создает в нем объект. Он сопряжен с delete
, который уничтожает объект и перерабатывает хранилище в свободное хранилище. В некотором смысле, стандартный new
вызывает внутреннее размещение new
, а стандартный delete
вызывает деструктор внутри.
Размещение new
- это то, как вы напрямую вызываете конструктор на каком-либо хранилище, и требуется для расширенного кода управления жизненным циклом. Если вы выполняете optional
, безопасный тип union
на выровненном хранилище или интеллектуальный указатель (с унифицированным хранилищем и не унифицированным временем жизни, например make_shared
), вы будете использовать размещение new
. Затем в конце определенного времени жизни объекта вы вызываете его деструктор. Подобно не размещению new
и delete
, размещение new
и ручные вызовы деструктора попадают парами.
Пользовательское размещение new
является еще одной причиной использования new
. Пользовательское размещение new
может использоваться для выделения ресурсов из неглобального распределения пула - распределения по выделенной области или распределения на общую страницу разделяемой памяти, распределения в общую память видеокарты и т.д. - и в других целях. Если вы хотите написать make_unique_from_custom
, который выделяет свою память с помощью специального места размещения new, вам нужно будет использовать ключевое слово new
. Пользовательское размещение new
может действовать как новое размещение (тем, что оно фактически не приобретает ресурсы, а скорее как ресурс каким-то образом передается), или он может действовать как стандартный new
(тем, что он приобретает ресурсы, возможно, используя аргументы переданы).
Пользовательское размещение delete
вызывается, если выбрано произвольное размещение new
, поэтому вам может потребоваться его написать. В С++ вы не называете пользовательское размещение delete
, оно (С++) вызывает вас (r overload).
Наконец, make_shared
и make_unique
являются неполными функциями, поскольку они не поддерживают пользовательские удалители.
Если вы пишете make_unique_with_deleter
, вы все равно можете использовать make_unique
для выделения данных, а .release()
- для вашего уникального обращения с делом. Если ваш дебит хочет записать свое состояние в буфер с указателем, а не в unique_ptr
или в отдельное выделение, вам нужно будет использовать размещение new
здесь.
Для 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
будет построены.
Ответ 3
Единственная причина, по которой я могу думать, это то, что иногда вы можете использовать пользовательский делетер со своими unique_ptr
или shared_ptr
. Чтобы использовать пользовательский удалён, вам нужно создать интеллектуальный указатель напрямую, передав результат new
. Даже это не часто, но на практике это происходит.
Кроме того, кажется, что make_shared
/make_unique
должен охватывать почти все виды использования.
Ответ 4
Я бы сказал, что единственной причиной для new
и delete
является реализация других видов интеллектуальных указателей.
Например, библиотека по-прежнему не имеет навязчивых указателей в качестве boost:: intrusive_ptr, что очень жалко, так как они превосходят по соображениям производительности общие указатели, как указывает Андрей Александреску.