Стандартное поведение для идентификаторов отличается от 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С++?
Ответы
Ответ 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 будет уничтожен.