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:
- 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 typebool
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 ifowned
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 ..*/
};
You may try out
boost::shared_ptr
Instead of
std::unique
, you may try to dostd::adjacent_find
in a loop. Then you'll just find all elements that are "the same" as by yourequal
. 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
What Is the Correct Way of Using C++11'S Range-Based For
Does Initialization Entail Lvalue-To-Rvalue Conversion? Is 'Int X = X;' Ub
Pure Virtual Destructor in C++
Difference Between Static_Cast≪≫ and C Style Casting
C/C++ With Gcc: Statically Add Resource Files to Executable/Library
How to Erase an Element from Std::Vector≪≫ by Index
Superiority of Unnamed Namespace Over Static
C++11 Does Not Deduce Type When Std::Function or Lambda Functions Are Involved
Why Does the Use of 'New' Cause Memory Leaks
How to Use Sdl2 in My Programs Correctly
Static Variables Initialisation Order
Is Std::Vector So Much Slower Than Plain Arrays
Should I Use Static_Cast or Reinterpret_Cast When Casting a Void* to Whatever