Нужно ли использовать std::destroy_at(p) вместо p->~T()
В С++17 добавили функцию destroy_at, которая просто вызывает деструктор объекта по указателю.
template<class T> void destroy_at(T* p) { p->~T(); }
(А в С++20 она еще научилась удалять элементы массивов, но вопрос именно про одиночные объекты)
Зачем нужна destroy_at, если можно самому вызвать деструктор?
Стоит ли теперь использовать destroy_at вместо ручного вызова деструктора?
Ответы (2 шт):
destroy_at(T*) добавили в рамках пропозала p0040, в котором добавляли destroy(Iter first, Iter last).
В первых версиях пропозала p0040 не было destroy_at(T*), и там было написано
template<class ForwardIterator>
void destroy(ForwardIterator begin, ForwardIterator end);
Effects:
typedef typename iterator_traits<ForwardIterator>::value_type __t;
while (begin != end)
{
begin->~__t();
++begin;
}
А в финальной версии пропозала добавили еще и destroy_at с комментарием что она "делает destroy проще и безопаснее".
И действительно, стало
template <class ForwardIterator>
void destroy(ForwardIterator first, ForwardIterator last);
Effects:
for(;first!=last;++first)
destroy_at(addressof(*first));
Из этого можно сделать вывод, что destroy_at(T*) нужна для случаев, когда мы не знаем тип T.
Например, как в примере выше, где у нас есть итератор.
А вот если бы у нас было template<class T> void destroy(T* first, T* last),
то destroy_at не нужна, т.к. мы можем написать first->~T();
Итого:
- Если можно написать
p->~T(), так и пишите. - Если "T" не известен, используйте
std::destroy_at(p).
Зачем нужна
destroy_at, если можно самому вызвать деструктор?
Кажется, что низачем не нужна.
Действительно немного удобнее вызывать, в том смысле что не нужно указывать тип T. Можно было бы написать x.~decltype(x)(), тогда тип не нужен, но тут нужно указывать объект дважды.
Но это такая мелочь, что я удивлен, что они добавили библиотечную функцию под это.
Один нормальный аргумент в пользу std::destroy_at() - это симметрия с std::construct_at().
Вот у construct_at есть конкретный смысл, потому что он волшебным образом сделан constexpr, тогда как placement-new (который он использует внутри себя) - не был constexpr аж до C++26.