Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How could one implement std::auto_ptr's copy constructor?

Back on my crazy AutoArray thingy... (quoting important bits from there:

class AutoArray
{
    void * buffer;
public:
    //Creates a new empty AutoArray
    AutoArray();
    //std::auto_ptr copy semantics
    AutoArray(AutoArray&); //Note it can't be const because the "other" reference
                           //is null'd on copy...
    AutoArray& operator=(AutoArray);
    ~AutoArray();
    //Nothrow swap
    // Note: At the moment this method is not thread safe.
    void Swap(AutoArray&);
};

)

Anyway, trying to implement the copy constructor. There's a piece of client code (not yet committed into bitbucket because it won't build) that looks like this:

AutoArray NtQuerySystemInformation(...) { ... };

AutoArray systemInfoBuffer = NtQuerySystemInformation(...);

This fails because the copy constructor takes a non-const reference as an argument .... but I don't see how you could modify the copy constructor to take a const reference, given that the source AutoArray used in the assignment is modified (and therefore wouldn't be const). You can't modify things to use pass by value of course, because it's the copy constructor and that'd be an infinite loop!

If I was using auto_ptr, this would be valid:

std::auto_ptr NtQuerySystemInformation(...) { ... };

std::auto_ptr systemInfoBuffer = NtQuerySystemInformation(...);

How then, can a class with auto_ptr's copy semantics be possible?

like image 589
Billy ONeal Avatar asked Dec 22 '10 22:12

Billy ONeal


People also ask

What is std :: auto_ptr?

std::auto_ptr auto_ptr is a smart pointer that manages an object obtained via new expression and deletes that object when auto_ptr itself is destroyed.

Why auto_ptr Cannot be used with STL?

Why is auto_ptr deprecated? It takes ownership of the pointer in a way that no two pointers should contain the same object. Assignment transfers ownership and resets the rvalue auto pointer to a null pointer. Thus, they can't be used within STL containers due to the aforementioned inability to be copied.

Is auto_ptr deprecated?

auto_ptr was fully removed in C++17.

Can we use pointer in copy constructor?

Technically, you could write a constructor that takes a pointer (although it's technically not a copy constructor in that case, based on the wording of the spec). However, this prevents you from using non-addressable results.


2 Answers

auto_ptr uses a dirty trick.

I'll use a dumbed-down class named auto_int to demonstrate just the copy construction functionality without bringing in any complexities introduced by templates or inheritance. I think the code is mostly correct, but it's untested. Our basic auto_int look something like this:

class auto_int
{
public:

    auto_int(int* p = 0) : p_(p) { }

    ~auto_int() { delete p_; }

    // copy constructor taking a non-const reference:
    auto_int(auto_int& other) 
        : p_(other.release()) { }

    int* release() 
    {
        int* temp = p_;
        p_ = 0;
        return temp;
    }

private:

    int* p_;
};

With this basic auto_int, we can't copy a temporary object. Our goal is to be able to write something like:

auto_int p(auto_int(new int()));

What we can do is use a helper class. For auto_ptr, this is called auto_ptr_ref. We'll call ours auto_int_ref:

class auto_int;

class auto_int_ref 
{
public:
    auto_int_ref(auto_int* p) : p_(p) { }

    auto_int& ref() { return *p_; }

private:
    auto_int* p_;
};

Basically, an instance of this class just stores a pointer to an auto_int and allows us to use it as a "reference" to an auto_int.

Then in our auto_int class we need two additional functions. We need another constructor that takes an auto_int_ref and we need a conversion operator that allows an auto_int to be implicitly converted to an auto_int_ref:

auto_int(auto_int_ref other)
    : p_(other.ref().release()) { }

operator auto_int_ref() { return this; }

This will allow us to "copy" a temporary while still having the copy constructor take a non-const reference. If we look again at our sample code:

auto_int p(auto_int(new int()));

What happens is we construct a new temporary auto_int and pass new int() to the constructor that takes an int*. This temporary is then converted to an auto_int_ref that points to it, using the operator auto_int_ref(), and the auto_int constructor that takes an auto_int_ref is used to initialize p.

like image 53
James McNellis Avatar answered Sep 20 '22 05:09

James McNellis


auto_ptr's copy ctor works by taking ownership away from the passed-in object. This is also a big part of the reason why auto_ptr can't be used in a vector or other STL collections.

This is not how most copy constructors work. Usually your copy constructor will just clone the passed-in object, so you can pass a const reference to it. But auto_ptr doesnt do this. It actually modified the original object. In this sense, its only a copy constructor by name, not by semantics.

EDIT2:

I'm trying to boil this down a bit. So effectively you're trying to do something with your class that will allow syntax similar to this:

#include <string>
#include <memory>
using namespace std;

auto_ptr<string> gimme()
{
    return auto_ptr<string>(new string("Hello"));
}

int main()
{
    auto_ptr<string> s = gimme();
}

...and you're wondering how to get the s = gimme() part to work. Right?

The secret here is in a proxy class, auto_ptr_ref described by the Standard in 20.4.5 whose purpose is "to allow auto_ptr objects to be passed to and returned from functions.":

namespace std {
  template <class Y> struct auto_ptr_ref {};
  template<class X> class auto_ptr {
  public:
    typedef X element_type;

    // 20.4.5.1 construct/copy/destroy:
    explicit auto_ptr(X* p =0) throw();
    auto_ptr(auto_ptr&) throw();
    template<class Y> auto_ptr(auto_ptr<Y>&) throw();
    auto_ptr& operator=(auto_ptr&) throw();
    template<class Y> auto_ptr& operator=(auto_ptr<Y>&) throw();
    auto_ptr& operator=(auto_ptr_ref<X> r) throw();
    ˜auto_ptr() throw();
    // 20.4.5.2 members:
    X& operator*() const throw();
    X* operator->() const throw();
    X* get() const throw();
    X* release() throw();
    void reset(X* p =0) throw();
    // 20.4.5.3 conversions:
    auto_ptr(auto_ptr_ref<X>) throw();
    template<class Y> operator auto_ptr_ref<Y>() throw();
    template<class Y> operator auto_ptr<Y>() throw();
  };
}
like image 20
John Dibling Avatar answered Sep 22 '22 05:09

John Dibling