In article <>,
"Joe Laughlin" <> wrote:
> Can someone explain why auto_ptr can't be used with STL containers? I've
> looked at various explanations, but I couldn't really understand any of
> them.
In addition to Leor's good comments I thought that I would add that any
sequence of auto_ptrs is dangerous if you get it too close to generic
code operating on sequences, even built-in arrays of auto_ptrs.
Consider this reasonable generic algorithm:
#include <iterator>
#include <algorithm>
template <class It, class Comp>
It
my_partition3(It first, It last, Comp comp)
{
typedef typename std::iterator_traits<It>::difference_type
difference_type;
typedef typename std::iterator_traits<It>::value_type
value_type;
difference_type len = std::distance(first, last);
switch (len)
{
case 0:
case 1:
return first;
case 2:
--last;
if (comp(*last, *first))
std::iter_swap(first, last);
return last;
}
It middle = first;
std::advance(middle, len/2);
--last;
if (comp(*middle, *first))
std::iter_swap(first, middle);
if (comp(*last, *first))
{
std::iter_swap(first, last);
std::iter_swap(middle, last);
}
else if (comp(*last, *middle))
std::iter_swap(middle, last);
++last;
value_type partition = *middle;
while (true)
{
while (true)
{
if (first == last)
return first;
if (!comp(*first, partition))
break;
++first;
}
--last;
while (true)
{
if (first == last)
return first;
if (comp(*last, partition))
break;
--last;
}
std::iter_swap(first, last);
++first;
}
}
This algorithm takes a sequence, looks at the first, middle and last
values, and then partitions the sequence based on the median of the
first, middle and last elements. Such an operation is useful in
sorting. Here's an example use:
#include <iostream>
#include <algorithm>
#include <functional>
int main()
{
int ia[9] = {0, 2, 3, 4, 5, 6, 7, 8};
unsigned size = sizeof(ia)/sizeof(ia[0]);
std::random_shuffle(ia, ia + size);
for (int* p = ia; p < ia + size; ++p)
std::cout << *p << ' ';
std::cout << '\n';
int* part = my_partition3(ia, ia+size, std::less<int>());
for (int* p = ia; p < part; ++p)
std::cout << *p << ' ';
std::cout << "- ";
for (int* p = part; p < ia+size; ++p)
std::cout << *p << ' ';
std::cout << '\n';
}
Sample output:
4 2 0 6 8 5 1 7 3
3 2 0 1 - 4 5 6 7 8
The algorithm looked at 4, 8, 3, chose the median 4, and partitioned the
range into those numbers less than 4, and those numbers greater than or
equal to 4.
You can use the same generic algorithm with a smart pointer (say
std::tr1::shared_ptr<int>) by making use of a proper comparator.
#include <iostream>
#include <algorithm>
#include <memory>
struct indirect_less
{
template <class T>
bool operator()(const T& x, const T& y) const
{ return *x < *y; }
};
int main()
{
typedef std::tr1::shared_ptr<int> Ptr;
Ptr ia[9];
Ptr* begin = ia;
unsigned size = sizeof(ia)/sizeof(ia[0]);
Ptr* end = ia + size;
for (int k = 0; k < size; ++k)
ia[k].reset(new int(k));
std::random_shuffle(begin, end);
for (Ptr* k = begin; k < end; ++k)
std::cout << **k << ' ';
std::cout << '\n';
Ptr* i = my_partition3(begin, end, indirect_less());
for (Ptr* k = begin; k < i; ++k)
std::cout << **k << ' ';
std::cout << "- ";
for (Ptr* k = i; k < end; ++k)
std::cout << **k << ' ';
}
4 2 0 6 8 5 1 7 3
3 2 0 1 - 4 5 6 7 8
However, you do not want to use auto_ptr with such a generic algorithm,
even though it compiles!
#include <iostream>
#include <algorithm>
#include <memory>
struct indirect_less
{
template <class T>
bool operator()(const T& x, const T& y) const
{ return *x < *y; }
};
int main()
{
typedef std::auto_ptr<int> Ptr;
Ptr ia[9];
Ptr* begin = ia;
unsigned size = sizeof(ia)/sizeof(ia[0]);
Ptr* end = ia + size;
for (int k = 0; k < size; ++k)
ia[k].reset(new int(k));
std::random_shuffle(begin, end);
for (Ptr* k = begin; k < end; ++k)
std::cout << **k << ' ';
std::cout << '\n';
Ptr* i = my_partition3(begin, end, indirect_less());
for (Ptr* k = begin; k < i; ++k)
std::cout << **k << ' ';
std::cout << "- ";
for (Ptr* k = i; k < end; ++k)
std::cout << **k << ' ';
}
The result is a crashing program. The culprit is this very reasonable
line in the generic algorithm:
value_type partition = *middle;
With most types a /copy/ of *middle* is put into partition (leaving
*middle unchanged). With auto_ptr, *middle is /moved/ into partition.
This fundamentally alters the logic of the algorithm in a way not
anticipated. If you're lucky, you'll get an immediate crash. If you're
unlucky, you'll get a subtle run time error that doesn't show up until
after shipping.
This is the main reason that auto_ptr was redesigned late in the game
for C++98 so that you could not create std::container<auto_ptr>. But
built-in arrays are still just as dangerous and compile just fine. The
problem is not with std::containers, but with auto_ptr:
Howard's rule about move: It is dangerous to design a class such that
it moves from lvalues with copy syntax. But it is safe to move from
rvalues (temporaries) with copy syntax. To safely move from lvalues,
one should use syntax other than copy.
auto_ptr is dangerous. Use it with care. Keep it away from generic
algorithms as you would gasoline away from flames. I hope to help put a
better alternative into C++0X:
http://www.open-std.org/jtc1/sc22/wg...77.htm#move_pt
r%20Example
-Howard