Подтвердить что ты не робот

Стандартное поведение для идентификаторов отличается от shared_ptr и unique_ptr в случае нулевых указателей?

ОК, так что сначала некоторые вещи, которые могут иметь значение:

Я использую компилятор Clang 3.1 в режиме С++ 11, а стандартная библиотека установлена ​​на libС++.

Я пытаюсь ознакомиться с С++ 11, и при этом я сталкивался с поведением, которое кажется странным. Это может быть причуда Clang или libС++, но я не могу говорить на С++ standardese, и у меня нет доступа к другим компиляторам с поддержкой С++ 11, поэтому я не могу это проверить, и я искал Интернет и переполнение стека в меру своих возможностей, не найдя ничего связанного... так вот мы идем:

При использовании shared_ptr/unique_ptr для реализации RAII для простого ресурса, кажется, что их поведение отличается от нулевых указателей при удалении. Я понимаю, что обычно не нужно удалять нулевой указатель, но я ожидал, что поведение будет по крайней мере совпадать между двумя умными указателями STL.

В конкретном случае рассмотрим следующий код:

{
    auto Deleter = [](void *){cout << "It later!" << endl;};
    shared_ptr<void> spDoSomethingLater(nullptr, Deleter);
    unique_ptr<void, void (*)(void *)> upDoSomethingLater(nullptr, Deleter);
    cout << "It now!" << endl;
}

Я ожидал один из следующих выходов из этого:

a) Если оба имени вызываются, хотя указатель имеет значение null:

"It now!"
"It later!"
"It later!"

b) Если ни один из операторов не вызван, поскольку указатель имеет значение null:

"It now!"

Но я не наблюдаю ни одного из этих случаев. Вместо этого я наблюдаю:

"It now!"
"It later!"

Это означает, что вызывается один, но не другой из удалений. При дальнейшем исследовании я обнаружил, что делеттер для shared_ptr вызывается независимо от того, содержит ли он нулевое значение, но деблокирование unique_ptr вызывается только в том случае, если оно не содержит нулевого значения.

Мои вопросы: Это действительно правильное поведение, указанное стандартом? Если да, то почему указанное поведение отличается друг от друга двумя типами STL таким образом? Если нет, это ошибка, которую я должен сообщить libС++?

4b9b3361

Ответ 1

Наблюдаемое поведение соответствует стандарту.

Для unique_ptr, 20.7.1.2.2/2 (эффекты деструктора) говорит

Эффекты: Если get() == nullptr нет эффектов. В противном случае get_deleter()(get()).

Для shared_ptr, 20.7.2.2.2/1 говорит, что дебетер следует вызывать, даже если он обертывает нулевой указатель:

Эффекты:

  • Если * это пусто или владение акциями с другим shared_ptr instance (use_count() > 1), побочных эффектов нет.
  • В противном случае, если * this принадлежит объект p и вызывается d, d(p).
  • В противном случае * этому принадлежит указатель p и вызывается delete p.

Важная деталь здесь - выражение "владеет объектом p". 20.7.2.2/1 говорит, что "объект shared_ptr пуст, если он не имеет указателя". 20.7.2.2.1/9 (соответствующий конструктор) говорит, что он "создает объект shared_ptr, которому принадлежит объект p и deleter d".

Итак, насколько я могу судить, этот вызов технически заставляет shared_ptr обладать нулевым указателем, что приводит к тому, что вызываемый делеттер вызывается. Контрастируйте это с конструктором без параметров, который, как говорят, оставляет shared_ptr "пустым".

Ответ 2

Да, это правильное поведение.

§20.7.1.2.2 [unique.ptr.single.dtor]/2:

unique_ptr деструктор

Эффекты: Если get() == nullptr нет эффектов. В противном случае get_deleter()(get()).

§20.7.2.2.2 [util.smartptr.shared.dest]/1:

shared_ptr деструктор

Эффекты:

  • Если *this пусто или владение акциями с другим экземпляром shared_ptr (use_count() > 1), побочных эффектов нет.
  • В противном случае, если *this принадлежит объект p и вызывается d, d(p).
  • В противном случае *this принадлежит указатель p и вызывается p.

Значит, не должно быть эффекта, поскольку shared_ptr пуст? Неверно, поскольку предоставление указателя делает shared_ptr не пустым, даже если указатель имеет значение null.

§20.7.2.2.1 [util.smartptr.shared.const]/8-10

shared_ptr конструкторы

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

Требуется: p должно быть конвертировано в T*....

Эффекты: Создает объект shared_ptr, которому принадлежит объект p и deleter d.

Постусловия: use_count() == 1 && get() == p.

Это означает, что shared_ptr принадлежит nullptr. Таким образом, отладчик будет вызван, когда shared_ptr будет уничтожен.