998. Smart pointer referencing its owner

Section: 23.11.1.2.5 [unique.ptr.single.modifiers] Status: C++11 Submitter: Pavel Minaev Opened: 2009-02-26 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [unique.ptr.single.modifiers].

View all issues with C++11 status.

Discussion:

Consider the following (simplified) implementation of std::auto_ptr<T>::reset():

void reset(T* newptr = 0) { 
   if (this->ptr && this->ptr != newptr) { 
     delete this->ptr; 
   } 
   this->ptr = newptr; 
} 

Now consider the following code which uses the above implementation:

struct foo { 
   std::auto_ptr<foo> ap; 
   foo() : ap(this) {} 
   void reset() { ap.reset(); } 
}; 
int main() { 
   (new foo)->reset(); 
} 

With the above implementation of auto_ptr, this results in U.B. at the point of auto_ptr::reset(). If this isn't obvious yet, let me explain how this goes step by step:

  1. foo::reset() entered
  2. auto_ptr::reset() entered
  3. auto_ptr::reset() tries to delete foo
  4. foo::~foo() entered, tries to destruct its members
  5. auto_ptr::~auto_ptr() executed - auto_ptr is no longer a valid object!
  6. foo::~foo() left
  7. auto_ptr::reset() sets its "ptr" field to 0 <- U.B.! auto_ptr is not a valid object here already!

[ Thanks to Peter Dimov who recognized the connection to unique_ptr and brought this to the attention of the LWG, and helped with the solution. ]

[ Howard adds: ]

To fix this behavior reset must be specified such that deleting the pointer is the last action to be taken within reset.

[ Alisdair adds: ]

The example providing the rationale for LWG 998 is poor, as it relies on broken semantics of having two object believing they are unique owners of a single resource. It should not be surprising that UB results from such code, and I feel no need to go out of our way to support such behaviour.

If an example is presented that does not imply multiple ownership of a unique resource, I would be much more ready to accept the proposed resolution.

[ Batavia (2009-05): ]

Howard summarizes:

This issue has to do with circular ownership, and affects auto_ptr, too (but we don't really care about that). It is intended to spell out the order in which operations must be performed so as to avoid the possibility of undefined behavior in the self-referential case.

Howard points to message c++std-lib-23175 for another example, requested by Alisdair.

We agree with the issue and with the proposed resolution. Move to Tentatively Ready.

Proposed resolution:

Change 23.11.1.2.5 [unique.ptr.single.modifiers], p5 (Effects clause for reset), and p6:

-5- Effects: If get() == nullptr there are no effects. Otherwise get_deleter()(get()). Assigns p to the stored pointer, and then if the old value of the pointer is not equal to nullptr, calls get_deleter()(the old value of the pointer). [Note: The order of these operations is significant because the call to get_deleter() may destroy *this. -- end note]

-6- Postconditions: get() == p. [Note: The postcondition does not hold if the call to get_deleter() destroys *this since this->get() is no longer a valid expression. -- end note]