Нужно ли использовать 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 шт):

Автор решения: Abyx

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).
→ Ссылка
Автор решения: HolyBlackCat

Зачем нужна destroy_at, если можно самому вызвать деструктор?

Кажется, что низачем не нужна.

Действительно немного удобнее вызывать, в том смысле что не нужно указывать тип T. Можно было бы написать x.~decltype(x)(), тогда тип не нужен, но тут нужно указывать объект дважды.

Но это такая мелочь, что я удивлен, что они добавили библиотечную функцию под это.


Один нормальный аргумент в пользу std::destroy_at() - это симметрия с std::construct_at().

Вот у construct_at есть конкретный смысл, потому что он волшебным образом сделан constexpr, тогда как placement-new (который он использует внутри себя) - не был constexpr аж до C++26.

→ Ссылка