Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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++?

like image 474
Bob Miller Avatar asked Jun 22 '12 21:06

Bob Miller


People also ask

What is the difference between the two smart pointers shared_ptr and unique_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

In what situation is a shared_ptr more appropriate than a unique_ptr?

Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

Is shared_ptr null?

false if the shared_ptr is a null pointer.

What is smart pointer when should we use it asked me to implement unique_ptr of my own?

std::unique_ptr s are useful when you want to tie the lifetime of the object to a particular block of code, or if you embedded it as member data inside another object, the lifetime of that other object. The object exists until the containing block of code is exited, or until the containing object is itself destroyed.


2 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".

like image 55
Jon Avatar answered Oct 14 '22 07:10

Jon


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.

like image 21
kennytm Avatar answered Oct 14 '22 06:10

kennytm