Why Is It Wrong to Use Std::Auto_Ptr≪≫ With Standard Containers

Why is it wrong to use std::auto_ptr with standard containers?

The C++ Standard says that an STL element must be "copy-constructible" and "assignable." In other words, an element must be able to be assigned or copied and the two elements are logically independent. std::auto_ptr does not fulfill this requirement.

Take for example this code:

class X
{
};

std::vector<std::auto_ptr<X> > vecX;
vecX.push_back(new X);

std::auto_ptr<X> pX = vecX[0]; // vecX[0] is assigned NULL.

To overcome this limitation, you should use the std::unique_ptr, std::shared_ptr or std::weak_ptr smart pointers or the boost equivalents if you don't have C++11. Here is the boost library documentation for these smart pointers.

Advantages/disadvantages of auto pointers

The main drawback of std::auto_ptr is that it has the transfer-of-ownership semantic. That makes it impossible to store std::auto_ptr in STL containers because the containers use the copy constructor when you store or get an element.

Also, another important aspect that i have noticed about the std::auto_ptr is that they cannot serve in the use of PIMPL idiom. This is because, they require the complete definition of the wrapped class's destructor. See this thread on c.l.c++.m for more detailed discussion.

Update: Transfer of ownership

class Test {};
std::auto_ptr<Test> ap_test_1(new Test);
std::auto_ptr<Test> ap_test_2(new Test);

ap_test_2 = ap_test_1; // here ap_test_1's ownership is transferred i.e. ap_test_2 is the
// new owner and ap_test_1 is NULL.

See this thread on Herb Sutter's site for more details on what this means when used in a STL container used by STL algorithms.

Passing an auto_ptr to a function effectively makes it a sink. Why?

This is because once you copy the auto_ptr into a variable, you forfeit the ownership of the pointer to the new variable.

When you have:

void foo(std::auto_ptr<bar> x);

and you call foo with an auto_ptr, you make a copy of the auto_ptr for foo's use. This effectively transfers ownership to foo and thus the pointer gets deleted after foo is finished.

This is a really surprising behavior that made me definitively stop using auto_ptr. For simple RAII inside a try block (the primary use case of auto_ptr, as described in books), use boost::scoped_ptr.

auto_ptr' and STL containers: writing an example of erroneous usage

I don't have MSVC right now, but judging from the error from g++, I guess this is the reason:

auto_ptr<T> only has a "copy constructor" which takes mutable references (§D.10.1.1[auto.ptr.cons]/2­–6):

auto_ptr(auto_ptr& a) throw();
template<class Y> auto_ptr(auto_ptr<Y>& a) throw();

But vector::push_back will accept a const reference (§23.3.6.1[vector.overview]/2).

void push_back(const T& x);

So it is impossible to construct an auto_ptr via push_back because no constructor takes a const reference.

Can I get away with putting auto_ptr in a STL container?

There are generally the following methods you can use in C++98:

  1. Define some pointer that will do what std::auto_ptr can't do. There was an old version of that thing, which contained an additional field of type bool that marked ownership. It was marked mutable, so it could be modified also in the object being read from when copying. The object was deleted at the end only if owned was true. Something like:

==

template <class T> class owning_ptr
{
T* ptr;
mutable bool owns;
public:
void operator =(T* src) { ptr = src; owns = true; }
owning_ptr(const owning_ptr& other)
{
// copy the pointer, but STEAL ownership!
ptr = other.ptr; owns = other.owns; other.owns = false;
}
T* release() { owns = false; return ptr; }
~owning_ptr() { if ( owns ) delete ptr; }
/* ... some lacking stuff ..*/
};

  1. You may try out boost::shared_ptr

  2. Instead of std::unique, you may try to do std::adjacent_find in a loop. Then you'll just find all elements that are "the same" as by your equal. If there's more than one element, you will erase them in place (you are allowed to do it because it's a list, so iterators remain valid).

Why does the C++ standard not change std::set to use std::less as its default template argument?

My guess is because they thought it wasn't so great improvement to break backward compatibility.

Another reason is because std::set with std::less<Key> existed even before C++11 (starting from C++03 I guess) and std::less<> appeared only in C++14. So if they move to std::set with std::less<> then you have to force C++14 when using set, or you have to make two kinds of sets - one for C++03 and one for C++14.

Also if you start from C++14 making set being std::less<>-based then your pre-C++14 code will start to behave differently sometimes. For example your code relied on calling Key's constructor for some reason when adding to set, then suddenly if you add option -std=c++14 your old code starts doing other things.

Usually STD people make such changes that switching from C++11 to C++14 doesn't break code's behaviour. Only downswitching from C++14 to C++11 can break something(usually non-compiling). In other words it is backward compatibility breaking change to use std::less<>. Such changes are usually only done by introducing new class name for such specializations.

Does std::list::remove method call destructor of each removed element?

Yes, removing a Foo* from a container destroys the Foo*, but it will not release the Foo. Destroying a raw pointer is always a no-op. It cannot be any other way! Let me give you several reasons why.

Storage class

Deleting a pointer only makes sense if the pointee was actually allocated dynamically, but how could the runtime possibly know whether that is the case when the pointer variable is destroyed? Pointers can also point to static and automatic variables, and deleting one of those yields undefined behavior.

{
Foo x;
Foo* p = &x;

Foo* q = new Foo;

// Has *q been allocated dynamically?
// (The answer is YES, but the runtime doesn't know that.)

// Has *p been allocated dynamically?
// (The answer is NO, but the runtime doesn't know that.)
}

Dangling pointers

There is no way to figure out whether the pointee has already been released in the past. Deleting the same pointer twice yields undefined behavior. (It becomes a dangling pointer after the first delete.)

{
Foo* p = new Foo;

Foo* q = p;

// Has *q already been released?
// (The answer is NO, but the runtime doesn't know that.)

// (...suppose that pointees WOULD be automatically released...)

// Has *p already been released?
// (The answer WOULD now be YES, but the runtime doesn't know that.)
}

Uninitialized pointers

It is also impossible to detect whether a pointer variable has been initialized at all. Guess what happens when you try to delete such a pointer? Once again, the answer is undefined behavior.

    {
Foo* p;

// Has p been properly initialized?
// (The answer is NO, but the runtime doesn't know that.)
}

Dynamic arrays

The type system does not distinguish between a pointer to a single object (Foo*) and a pointer to the first element of an array of objects (also Foo*). When a pointer variable is destroyed, the runtime cannot possibly figure out whether to release the pointee via delete or via delete[]. Releasing via the wrong form invokes undefined behavior.

{
Foo* p = new Foo;

Foo* q = new Foo[100];

// What should I do, delete q or delete[] q?
// (The answer is delete[] q, but the runtime doesn't know that.)

// What should I do, delete p or delete[] p?
// (The answer is delete p, but the runtime doesn't know that.)
}

Summary

Since the runtime cannot do anything sensible with the pointee, destroying a pointer variable is always a no-op. Doing nothing is definitely better than causing undefined behavior due to an uninformed guess :-)

Advice

Instead of raw pointers, consider using smart pointers as the value type of your container, because they take responsibility for releasing the pointee when it is no longer needed. Depending on your need, use std::shared_ptr<Foo> or std::unique_ptr<Foo> . If your compiler does not support C++0x yet, use boost::shared_ptr<Foo>.

Never, I repeat, NEVER EVER use std::auto_ptr<Foo> as the value type of a container.



Related Topics



Leave a reply



Submit