Почему unique_ptr сбрасывает перегрузку (указатель p = pointer()) и сбрасывает (nullptr_t)?

#c #arrays #c 11 #overloading #unique-ptr

#c #массивы #c 11 #перегрузка #уникальный-ptr

Вопрос:

В соответствии с http://en.cppreference.com/w/cpp/memory/unique_ptr/reset,

 void reset( pointer ptr = pointer() );

template< class U > 
void reset( U ) = delete;

void reset( std::nullptr_t p );
  

1) Учитывая current_ptr , что указатель, которым управлял *this , выполняет
следующие действия в этом порядке:
Сохраняет копию текущего указателя old_ptr = current_ptr ;
Перезаписывает текущий указатель аргументом current_ptr = ptr ;
Если старый указатель не был пустым, удаляется ранее управляемый объект if(old_ptr != nullptr) get_deleter()(old_ptr) .

2) В специализации для динамических массивов std::unique_ptr<T[]> этот элемент шаблона предусмотрен для предотвращения использования reset() с указателем на производный (что приведет к неопределенному поведению с массивами).

3) В специализации для динамических массивов std::unique_ptr<T[]> , третья перегрузка необходима, чтобы разрешить сброс nullptr (который в противном случае был бы запрещен перегрузкой шаблона). Эквивалентно reset(pointer())

Теперь это reset(nullptr) эквивалентно тому reset(pointer()) , почему последнее существует?

Если я хочу сбросить массив формы unique_ptr, почему я не могу просто использовать rest(pointer()) ?

Комментарии:

1. Я на самом деле нахожу метод delete ion более интересным…

2. Потому что было бы глупо иметь p.reset(nullptr) ошибку компиляции?

3. Обратите внимание, что std::unique_ptr<T[]> принимает T * и T * только как тип указателя для его конструктора и для reset метода. Примечательно, что он не принимает указатели на типы, отличные от T , даже если они преобразуются в указатели на T . Другими словами: без этой перегрузки p.reset(nullptr); не будет компилироваться. Это было бы довольно глупо. (По той же причине есть unique_ptr(nullptr_t) ctor .)

Ответ №1:

The

 template< class U > 
void reset( U ) = delete;
  

был бы выбран для вызова с nullptr аргументом, если бы не

 void reset( std::nullptr_t p );
  

Вот почему он существует, чтобы разрешить вызов with nullptr .


Пример (компиляция с FIX определенным для подавления ошибки компиляции):

 #include <cstddef>      // std::nullptr_t

struct S
{
    void reset( char* ) {}

    template< class Type >
    void reset( Type ) = delete;

    #if FIX
    void reset( std::nullptr_t ) {}
    #endif
};

auto main() -> int
{
    S().reset( nullptr );    // Fails when FIX is not defined.
}
  

Комментарии:

1. Почему reset(nullptr) не может быть преобразован в reset(указатель)? Форма № 1 более конкретна, чем форма № 2. Так что, я думаю, даже если нет формы № 3, разрешение перегрузки все равно может работать.

2. @xmllmx: шаблон точно соответствует типу, в то время как не-шаблон требует преобразования.

Ответ №2:

reset это реализовано как

 pointer old = this->ptr;
this->ptr= newPointer;
delete[] old;
  

Шаблонная перегрузка удаляется для массивов, чтобы предотвратить следующий случай

 class foo{};
class bar : public foo {};

foo* managedPointer = new foo[3];
bar* newPointer = new bar[5];

foo* old = managedPointer;
managedPointer = newPointer;
delete[] old;
  

Что является неопределенным поведением. Раздел 5.3.5 параграф 3:

[…] Во второй альтернативе (удалить массив), если динамический тип удаляемого объекта отличается от его статического типа, поведение не определено.

Поскольку удаленные функции по-прежнему участвуют в разрешении перегрузки и reset(U) обеспечивают лучшее соответствие для nullptr than reset(pointer) , существует дополнительная перегрузка для разрешения reset(nullptr) , что в противном случае привело бы к ошибке компилятора и, следовательно, привело бы к несогласованному интерфейсу между версией массива и указателя.