Mark Meretzky
Mark Meretzky

Reputation: 99

Example of C++ std::vector<std::auto_ptr<T>> that compiles but fails

What would be a simple C++ program where a std::vector<std::auto_ptr<T>> compiles but fails to execute correctly, whereas the same program with std::vector<std::unique_ptr<T>> compiles and works correctly, for some data type T?

I know that std::auto_ptr has been deprecated or removed; I just want an example involving containers to motivate why it was deprecated or removed.

I'm using g++-10 -std=c++20 on MacOS Big Sur version 11.2.1.

Upvotes: 2

Views: 112

Answers (2)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123114

The problem with auto_ptr is that it can be copied and making a copy modifies the original:

a = b;   // transfers ownership from b to a

This is similar to what moving does now, just that at the time of auto_ptr there was no move semantics in the language. Now that C++ has move semantics transfer of ownership can be expressed more clearly:

a = std::move(b);

Nobody would/should expect that b is not modified in that line.

However, with a = b it is commonly assumed, that b is not modified. auto_ptr breaks that assumption. Consider for example:

template <typename P>
void foo() {
    std::vector<P> x;
    x.resize(42);
    int i=0;
    for (auto& e : x) e.reset(new int(i++));
    
    for (auto e : x) {
        std::cout << *e << "\n";        
    }
    for (auto e : x) {
        std::cout << *e << "\n";        
    }    
}

With P=std::unique_ptr<int> this will cause a compiler error:

<source>:17:15: error: call to deleted constructor of 'std::unique_ptr<int, std::default_delete<int> >'
    for (auto e : x) {
              ^ ~

While it compiles with P=std::auto_ptr<int> but is undefined behavior (eg segfault here: https://godbolt.org/z/93hdse), because it dereferences null pointers.

Similar issue with any algorithm assumes it is "ok" to copy elements. For example a comparator for auto_ptr that takes parameters by value does compile but causes havoc:

auto compare = [](auto a,auto b) { return *a < *b; }
std::sort(x.begin(), x.end(),compare);   // BOOM !

Not always is it so obvious that a copy is being made, algorithms may copy elements internally when elements are copyable.

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 597941

std::auto_ptr simply cannot be used in standard containers at all. It does not maintain proper semantics under that situation. Which is one of the reasons why move semantics and std::unique_ptr were invented in C++11 in the first place. std::auto_ptr was deprecated in C++11, and removed completely in C++17. So don't use it at all in modern coding.

The official reason why std::auto_ptr was deprecated is detailed here:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1856.html#20.4.5%20-%20Class%20template%20auto_ptr

The example given uses std::sort() on a std::vector<std::auto_ptr<int>>:

With such a design, one could put auto_ptr into a container:

vector<auto_ptr<int> > vec;

However field experience with this design revealed subtle problems. Namely:

sort(vec.begin(), vec.end(), indirect_less());

Depending upon the implementation of sort, the above line of reasonable looking code may or may not execute as expected, and may even crash! The problem is that some implementations of sort will pick an element out of the sequence, and store a local copy of it.

...
value_type pivot_element = *mid_point;
...

The algorithm assumed that after this construction that pivot_element and *mid_point were equivalent. However when value_type turned out to be an auto_ptr, this assumption failed, and subsequently so did the algorithm.

The fix to this problem was to make auto_ptr inhospitable to containers by disallowing "copying" from a const auto_ptr. With such an auto_ptr, one gets a compile time error if you try to put it in a container.

The conclusion at the end comes down to this:

Calling any generic code, whether std or not, that will operate on auto_ptr is risky because the generic code may assume that something that looks like a copy operation, actually is a copy operation.

Conclusion:

One should not move from lvalues using copy syntax. Other syntax for moving should be used instead. Otherwise generic code is likely to initiate a move when a copy was intended.

auto_ptr moves from lvalues using copy syntax and is thus fundamentally unsafe.

Upvotes: 6

Related Questions