views:

97

answers:

2

EDIT: I should've mentioned, I was looking at the documentation for Boost's ptr_sequence_adapter and it claims that their adapter for template< class U > void push_back( ::std::auto_ptr<U> x ); is equivalent to doing vec.push_back(autoPtr.release()); and also provides the strong exception guarantee. And then I realized I was confusing their description of the effect of the implementation for what the implementation actually was, and so this question is semi-nonsensical. I'm just leaving it here for posterity.

To me, it seems the call to std::auto_ptr<t> would succeed, and then the call to std::vector<t*>::push_back could throw an exception, and the pointer would be leaked.

It seems like you'd have to do this instead:

vec.push_back(0); //Attempts to allocate space in vector, could throw bad_alloc
vec.back() = autoPtr.release(); //Provides nothrow
A: 

Transferring an object out of an auto_ptr releases that auto_ptr's control of the object. It no longer gets the benefits of the auto_ptr. You would have to place the object in another smart pointer in order to become exception safe again, or a container object that gives exception safety guarantees (if there are any?).

Please note that std::auto_ptr cannot be used directly in STL containers. For example, std::vector<auto_ptr<int> > will break. You can have pointers to auto_ptrs, and store the auto_ptrs somewhere else, but auto_ptr copy semantics doesn't jive with STL containers, which rely on copying.

Merlyn Morgan-Graham
@Merlyn: If the vector is controlled by a noncopyable object which deletes all the pointers in the vector in it's destructor, then you can still maintain exception safety.
Billy ONeal
@Billy ONeal: Good point. However, if one of the destructors throws (which admittedly is dangerous to begin with, for other reasons), then the rest won't be freed.
Merlyn Morgan-Graham
@Merlyn: Destructors that throw aren't ever exception safe anyway, because if they throw while the stack is being unwound then you have undefined behavior.
Billy ONeal
@Billy: In that case, the behavior is well-defined: `std::terminate` is called.
James McNellis
@James. Oops. But if you're terminating, you don't care about exception safety either :) Your process is toast.
Billy ONeal
@Billy: Yeah. If you end up in `std::terminate`, you fail at exception safety :-P
James McNellis
@Billy: Yep, that's what I alluded to when saying "is dangerous to begin with, for other reasons". @James: lol at fail :)
Merlyn Morgan-Graham
+1  A: 

This is specifically a feature of the Boost pointer containers library.

The base push_back member function is defined as:

void push_back( value_type x )  // strong               
{
    this->enforce_null_policy( x, "Null pointer in 'push_back()'" );

    auto_type ptr( x );           // notrow
    this->base().push_back( x );  // strong, commit
    ptr.release();                // nothrow
}

(from the ptr_sequence_adapter.hpp header)

So, the push_back function itself takes ownership of the pointer and if reallocation fails, it takes responsibility for deleting the pointer.

The push_back overload that takes an auto_ptr is defined in terms of the base push_back function:

template< class U >
void push_back( std::auto_ptr<U> x )
{
    push_back( x.release() );
}

The pointer is released before the call to the base push_back, which is okay because the base push_back function has a strong guarantee that it will delete the pointer if an exception is thrown.

James McNellis
I think that the phrase "has _a_ strong guarantee" is potentially confusing. In exception safety speak _the_ strong guarantee is that either the operation succeeds as a whole or and exception is thrown and the state of all objects is otherwise precisely as it was before the call was made. If the base `push_back` provided _the_ strong guarantee it would leave the object alone (not take ownership, not deleted as the original state was). This is less useful than the guarantee it does provide, that it will always take ownership of the object.
Charles Bailey
I'm not 100% sure but you can definitely argue that the `push_back` which takes an `auto_ptr` does provide the strong guarantee. If you consider the caller to be responsible for creating and destroying the parameter `auto_ptr` instance then if `push_back` fails then it has no effect on the state. The fact that the object is correctly destroyed is because the caller cleans up the by-value parameters. Of course, there's no way to 'rescue' the object if you wanted to do something else with it if you failed to add it to the `ptr_vector` but this is an artifact of the interface.
Charles Bailey
@Charles: Actually, it provides the strong guarantee because it's implemented-in-terms-of the other push_back, which also provides the strong guarantee. And the caller isn't responsible for freeing the object in `auto_ptr`'s destructor because it's passed into push_back by value.
Billy ONeal
@Billy ONeal: Your argument is fallacious because the `push_back` which takes a raw pointer does _not_ provide the strong guarantee. (It provides a different, potentially more useful guarantee.) If it provided the strong guarantee then the state of the program if the operation failed would be exactly the same as before the operation started. This isn't the case as `push_back` will destroy the pointed to object if the base `push_back` operation fails.
Charles Bailey