3

This question raised after reading this tutorial: http://www.cprogramming.com/tutorial/auto_ptr.html

There you can find the following statement: A subtle consequence of this behavior is that auto_ ptrs don't work well in all scenarios. For instance, using auto _ptr objects with the standard template library can lead to problems as some functions in the STL may make copies of the objects in containers such as the vector container class. One example is the sort function, which makes copies of some of the objects in the container being sorted. As a consequence, this copy can blithely delete the data in the container!

Most of the papers concerning 'auto_ptr' tell us something like following: "Never use 'auto_ptr' with STL containers! They often copy their elements while performing intrinsic operations. For example consider sort on std::vector".

So my goal is to write the code sample that illustrates this point or prove that such examples are only theoretically true and weird on practice.

P.S. @everybody_who_also_knows_that_auto_ptr_is_deprecated I also know this. But don't you consider technical reasons (legacy code or old compiler) that may not allow new pointer containers usage? And moreover this question is about old and bad (if you'd like) auto_ptr.

nickolay
  • 3,643
  • 3
  • 32
  • 40
  • 4
    I believe you don't need to be concerned with this anymore since `auto_ptr` **as a whole** is deprecated nowadays. Use `unique_ptr` instead, you can keep it inside containers too. – Kos Dec 25 '11 at 17:06
  • 3
    @Kos I've also read the new standard. Please, answer my question. Stop off-topic postings. – nickolay Dec 25 '11 at 17:08
  • @DaddyM Well, it _is_ on topic. – Etienne de Martel Dec 25 '11 at 17:18
  • 2
    @EtiennedeMartel please, read P.S. section of the post. – nickolay Dec 25 '11 at 17:22
  • Still worth to mention this for future readers who may not know that `auto_ptr` is the past. – Kos Dec 25 '11 at 17:33
  • possible duplicate of [Why is it wrong to use std::auto_ptr<> with standard containers?](http://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers) – Ben Voigt Dec 25 '11 at 17:48
  • @BenVoigt I've already read this topic. If you've read both you understand that the topics are not about the same. Moreover, code provided in answer to the topic you've pointed out is wrong and couldn't be compiled. – nickolay Dec 25 '11 at 18:20
  • @DaddyM: If you have something to add to the discussion, answer the other question (with a real answer, not just a link), which is tagged FAQ. Or add some comments to it, which is the appropriate place to put links. But don't make more questions on the same exact topic. – Ben Voigt Dec 25 '11 at 20:04
  • Anyway, the answers in the other question do explain exactly what's wrong with your wrapper: Your object still doesn't properly support copy semantics. – Ben Voigt Dec 25 '11 at 20:07
  • 1
    @BenVoigt The purpose of `auto_ptr_my` is to make `auto_ptr` implicitly working with `vector`. Semantics (including ownership loss) must be saved. – nickolay Dec 25 '11 at 20:57
  • @DaddyM: `std::vector` does not work with ownership loss semantics. Period. The standard allows `std::vector` to make a copy of any element, at any time, for internal use. If that destroys your data, you have a problem. – Ben Voigt Dec 26 '11 at 16:45

5 Answers5

4

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.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 1
    Ok. Thanks. But I'm not interested in error message and cause. I'm interested in correct code that will illustrate the point I've quoted in my post. – nickolay Dec 25 '11 at 18:29
  • 1
    @DaddyM: `std::vector > vec2 = vec;`, and then iterate `vec`. – kennytm Dec 25 '11 at 18:58
  • 1
    Thanks. You've showed a good way to get `auto_ptr_my` down! I've marked your comment. – DaddyM 8 mins ago – nickolay Dec 25 '11 at 19:47
  • In C++11, another overload of `std::vector::push_back` has an rvalue reference parameter. So while, for an appropriate `std::auto_ptr` variable `a`, the call `push_back(a)` will not compile, `push_back(std::move(a))` will both compile and have the desired effect (moving ownership to `v` from `a`, which is nullified). – Marc van Leeuwen Aug 27 '14 at 10:00
  • @MarcvanLeeuwen Note that in C++11 you're supposed to use `std::unique_ptr`. `auto_ptr` has been deprecated. – kennytm Aug 27 '14 at 10:32
  • @kennytm, copy constructor (or even assignment operator) requires const-ref to members. So in this case compiler would block it. – tartaruga_casco_mole Jun 06 '20 at 23:30
2

From what you write, it seems that you already know everything that there is to know about containers of auto_ptrs and why they are unsafe.

Therefore, I assume that your interest in containers of auto_ptrs is purely teaching oriented. I understand your frustration in attempting to build a deliberate counter-example: in fact, most implementers of standard containers have put in place work-arounds to avoid accidentally triggering the broken semantics of auto_ptrs.

So, here's an example that I have written myself precisely for teaching:

class MyClass {
  int a;
public:
  MyClass (int i) : a(i) {  }
  int get() const { return a; }
};

int main() {
  constexpr unsigned size = 10;
  std::vector< std::auto_ptr<MyClass> > coap;
  coap.resize(size);

  for (unsigned u=0; u<size; u++)
    coap[u] = std::auto_ptr<MyClass>( new MyClass( rand() % 50 ));

  std::sort( coap.begin(), coap.end(),
           []( std::auto_ptr<MyClass> a,
               std::auto_ptr<MyClass> b) { return a->get() < b->get(); }); 
}

Compiling it with g++ 4.9.2 will lead to an executable that will segfault nicely.

You can rewrite the example above even more concisely by using type deduction:

  std::sort( coap.begin(), coap.end(),
           []( auto a, auto b) { return a->get() < b->get(); }); 

Note that the problem is not in the specific implementation of std::sort, which seems to be auto_ptr-safe. It is rather in the comparison lambda function I am passing to std::sort, that deliberately accepts its arguments by value, thus destroying the objects in the container every time a comparison is performed.

If you changed the lambda so that it receives its arguments by reference, as shown below, most STL implementations would actually behave correctly, even if you are doing something that is conceptually wrong.

  std::sort( coap.begin(), coap.end(),
           []( const std::auto_ptr<MyClass> & a,
               const std::auto_ptr<MyClass> & b) { return a->get() < b->get(); }); 

Good luck!

scarpaz
  • 395
  • 4
  • 6
1

STEP 1 Lets' solve this problem in a straight way:

#include <iostream>
#include <vector>
#include <algorithm>

template<> struct std::less<std::auto_ptr<int>>: public std::binary_function<std::auto_ptr<int>, std::auto_ptr<int>, bool> {
  bool operator()(const std::auto_ptr<int>& _Left, const std::auto_ptr<int>& _Right) const
  { // apply operator< to operands
    return *_Left < *_Right;
  }
};

int wmain() {
  using namespace std;

  auto_ptr<int> apai(new int(1)), apai2(new int(2)), apai3(new int(3));
  vector<auto_ptr<int>> vec;
  vec.push_back(apai3);
  vec.push_back(apai);
  vec.push_back(apai2);

  for ( vector<auto_ptr<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << i->get() << L'\t';

  vector<int> vec2;
  vec2.push_back(3);
  vec2.push_back(2);
  vec2.push_back(5);

  sort(vec2.begin(), vec2.end(), less<int>());

  sort(vec.begin(), vec.end(), less<auto_ptr<int>>());

  return 0;
}

On MSVCPP11 the error text is following: _Error 1 error C2558: class 'std::auto_ptr<Ty>': no copy constructor available or copy constructor is declared 'explicit' c:\program files (x86)\microsoft visual studio 11.0\vc\include\xmemory0 608

The conclusion is: I even cannot compile such example. Why do they prevent me to do something that I cannot compile?? Their preventions are not always true.


STEP 2

We cannot use auto_ptr as vector element type directly due to auto_ptr design. But we can wrap `auto_ptr' in the way presented below.

#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
#include <functional>

template<typename T> class auto_ptr_my: public std::auto_ptr<T> {
public:
  explicit auto_ptr_my(T *ptr = 0) {
    this->reset(ptr);
  }
  auto_ptr_my<T> &operator=(const auto_ptr_my<T> &right) {
    *(static_cast<std::auto_ptr<T> *>(this)) = *(static_cast<std::auto_ptr<T> *>(const_cast<auto_ptr_my *>(&right)));
    return *this;
  }
  auto_ptr_my(const auto_ptr_my<T>& right) {
    *this = right;
  }
};

namespace std
{
template<> struct less<auto_ptr_my<int> >: public std::binary_function<auto_ptr_my<int>, auto_ptr_my<int>, bool> {
  bool operator()(const auto_ptr_my<int>& _Left, const auto_ptr_my<int>& _Right) const
  { // apply operator< to operands
    return *_Left < *_Right;
  }
};
}

int wmain() {
  using namespace std;

  auto_ptr_my<int> apai(new int(1)), apai2(new int(2)), apai3(new int(3));

  vector<auto_ptr_my<int>> vec;
  vec.push_back(apai3);
  vec.push_back(apai);
  vec.push_back(apai2);

  for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << **i << L'\t';

  sort(vec.begin(), vec.end(), less<auto_ptr_my<int>>());

  for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
    wcout << **i << L'\t';

  return 0;
}

This code works well showing that auto_ptr can be used with vector and sort with no memory leaks and crashes.


STEP 3 As KennyTM posted below:

add this code before return 0; statement:

std::vector<auto_ptr_my<int>> vec2 = vec;

for ( vector<auto_ptr_my<int>>::const_iterator i(vec2.cbegin()) ; i != vec2.cend() ; ++i )
  wcout << **i << L'\t';
wcout << std::endl;

for ( vector<auto_ptr_my<int>>::const_iterator i(vec.cbegin()) ; i != vec.cend() ; ++i )
  wcout << **i << L'\t';
wcout << std::endl;

...and get memory leaks!


CONCLUSION Sometimes we can use auto_ptr with containers without visible crash, sometimes not. Anyway it is bad practice. But don't forget that auto_ptr is designed in such way that you cannot use it straight with STL containers and algorithms: against you have to write some wrapper code. At last using auto_ptr with STL containers is for your own risk. For example, some implementations of sort will not lead to the crash while processing vector elements, but other implementations will lead directly to the crash.

This question has academic purposes. Thanks to KennyTM for providing STEP 3 crash example!

Dmitry Kuzminov
  • 6,180
  • 6
  • 18
  • 40
nickolay
  • 3,643
  • 3
  • 32
  • 40
0

The conclusion is: I even cannot compile such example. Why do they prevent me to do something that I cannot compile??

IIRC, it is the other way around: the compiler vendor takes steps to prevent you from compiling something that you shouldn't be able to do. The way the standard is written, they could implement the library in a way that the code compiles, and then fails to work properly. They can also implement it this way, which is seen as superior because it's one of those few times where the compiler is actually allowed to prevent you from doing something stupid :)

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • I understand you. But let me say the opposite thing. As KennyTM has mentioned above: the reason why I could not compile the code is straightforward: `auto_ptr` design does not allow me to do this. This design is proclaimed in the C++ standard and looks the following way: `auto_ptr(auto_ptr& _Right) throw();` So, that is not compiler feature. This bases on `auto_ptr` design. Consequently my question is amplified: *why we're telling each other: "Do not use `auto_ptr` with STL containers" while it's deliberately impossible???* – nickolay Dec 25 '11 at 18:50
  • Because it's possible to write a vector implementation that uses such a constructor, and at least originally, there was nothing in the Standard to disallow it from doing so. – Karl Knechtel Dec 26 '11 at 02:16
-2

The right answer is "never use auto_ptr at all" -- its deprecated and never became part of the standard at all, for precisely the reasons outlined here. Use std::unique_ptr instead.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 2
    This is a non-answer, and just plain wrong. `std::auto_ptr` was part of the C++98 standard (and was not deprecated there). It became deprecated only in the C++11 standard, which is also where `unique_ptr` was first officially introduced. (The current question was not tagged c++11, though posed in 2011). And you'll have a hard time writing a program using `std::unique_ptr` that works (in C++11), while changing it to `std::auto_ptr` makes it fail (still in C++11 of course; by the way, the opposite is easy). – Marc van Leeuwen Aug 27 '14 at 09:37