Does the standard behavior for deleters differ between shared_ptr and unique_ptr in the case of null pointers?

OK, so first some things that might be relevant:

I'm using the Clang 3.1 compiler, in C++11 mode, with the standard library set to libc++.

I'm trying to familiarize myself with C++11, and in so doing I ran across behavior that seems odd. It may be a quirk of Clang or libc++ but I can't speak C++ standardese and I have no access to other compilers with C++11 support so I can't really check it, and I've searched the internet and Stack Overflow to the best of my ability without finding anything related...so here we go:

When using shared_ptr / unique_ptr to implement RAII for a simple resource, it seems that their behavior differs with respect to null pointers upon deletion. I realize that normally it's not necessary to delete a null pointer, but I had expected the behavior to at least match between the two STL smart pointers.

For the specific case, consider the following code:

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

I would have expected one of the following outputs from this:

a) If both deleters are called even though the pointer is null:

"It's now!"
"It's later!"
"It's later!"

b) If neither deleter is called because the pointer is null:

"It's now!"

But I observe neither of these cases. Instead, I observe:

"It's now!"
"It's later!"

Which means one but not the other of the deleters is being called. Upon further investigation, I found that the deleter for shared_ptr is called regardless of whether it holds a null value, but unique_ptr's deleter is only called if it does not hold a null value.

My questions: Is this actually the correct behavior as specified by the standard? If so, why does the specified behavior differ between the two STL types in this manner? If not, is this a bug I should report to libc++?

Answers


The observed behavior is in accordance with the standard.

For unique_ptr, 20.7.1.2.2/2 (destructor effects) says

Effects: If get() == nullptr there are no effects. Otherwise get_deleter()(get()).

For shared_ptr, 20.7.2.2.2/1 says that the deleter should be called even if it wraps the null pointer:

Effects:

  • If *this is empty or shares ownership with another shared_ptr instance (use_count() > 1), there are no side effects.
  • Otherwise, if *this owns an object p and a deleter d, d(p) is called.
  • Otherwise, *this owns a pointer p, and delete p is called.

The important detail here is the expression "owns an object p". 20.7.2.2/1 says that "a shared_ptr object is empty if it does not own a pointer". 20.7.2.2.1/9 (the relevant constructor) says that it "constructs a shared_ptr object that owns the object p and the deleter d".

So as far as I can tell, that invocation technically makes the shared_ptr own the null pointer, which results in the deleter being called. Contrast this with the parameterless constructor which is said to leave the shared_ptr "empty".


Yes it is the correct behavior.

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

unique_ptr destructor

Effects: If get() == nullptr there are no effects. Otherwise get_deleter()(get()).

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

shared_ptr destructor

Effects:

  • If *this is empty or shares ownership with another shared_ptr instance (use_count() > 1), there are no side effects.
  • Otherwise, if *this owns an object p and a deleter d, d(p) is called.
  • Otherwise, *this owns a pointer p, and delete p is called.

So there should be no effect since the shared_ptr is empty? Wrong, because providing the a pointer makes the shared_ptr not empty, even if the pointer is null.

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

shared_ptr constructors

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);

Requires: p shall be convertible to T*. …

Effects: Constructs a shared_ptr object that owns the object p and the deleter d.

Postconditions: use_count() == 1 && get() == p.

This means the shared_ptr owns the nullptr. Thus the deleter will be called when the shared_ptr is destroyed.


Need Your Help

Time format in MySql database

mysql csv time

In the table theGlobal from MySql Database I have the field theTime set on char 50 and the field theType set on char 1.

Sensible Way to Pass Web Data in XML to a SQL Server Database

javascript sql-server xml asp-classic

After exploring several different ways to pass web data to a database for update purposes, I'm wondering if XML might be a good strategy. The database is currently SQL 2000. In a few months it will...