|The C++ Standard Library: auto_ptr and auto_ptr_ref|
This page contains details about the motivation of auto_ptr_ref as part of the auto_ptr class.
I appreciate any constructive feedback or additional hints for this page.
According to the concept of auto_ptrs, it is possible to transfer ownership into a function by using a constant reference. This is very dangerous because people usually expect that an object won't get modified when you pass it as a constant reference. Fortunately, there was a late design decision that made auto_ptrs less dangerous. By some tricky implementation techniques, transfer of ownership is not possible with constant references. In fact, you can't change the ownership of any constant auto_ptr:
template <class T>
void dangerous_function(std::auto_ptr<T>); // Caveat: gets ownership of passed argument
const std::auto_ptr<int> p(new int); // safe auto_ptr
*p = 42; // OK, change value to which p refers
dangerous_function(p); // COMPILE-TIME ERROR (which is great!)
Auto_ptr_ref is part of the implementation of auto_ptr to enable this behavior.
The rest of the class auto_ptr (auxiliary type auto_ptr_ref and functions using it) consists of rather tricky conversions that enable you to use copy and assignment operations for nonconstant auto_ptrs but not for constant auto_ptrs (see page 44 of my book for details). These tricky conversations are sometimes called the Colvin-Gibbons trick. The following is a quick explanation (thanks to Bill Gibbons for pointing it out).
We have the following two requirements:
The names rvalue and lvalue come originally from the assignment expression expr1 = expr2, in which the left operand expr1 must be a (modifiable) lvalue ("left value"). However, an lvalue is perhaps better considered as representing an object locator value. Thus, it is an expression that designates an object by name or address (pointer or reference). Lvalues need not be modifiable. For example, the name of a constant object is a nonmodifiable lvalue. All expressions that are not lvalues are rvalues. In particular, temporary objects created explicitly (T()) or as the result of a function call are rvalues.
Because auto_ptr is a class, this must be done using a constructor.
An ordinary copy constructor can copy an rvalue, but to do so it must declare its parameter as a reference to a const object. To use an ordinary constructor to copy an auto_ptr we would have to declare the data member containing the real pointer mutable so that it could be modified in the copy constructor. But this would allow you to write code that copies auto_ptr objects that were actually declared const, transferring their ownership in contradiction to their constant status.
The alternative is to find a mechanism to enable an rvalue to be converted to an lvalue. A simple operator conversion function to reference type does not work because an operator conversion function is never called to convert an object to its own type (remember that the reference attribute is not part of the type). Thus, the auto_ptr_ref class was introduced to provide this convert-to-lvalue mechanism. The mechanism relies on a slight difference between the overloading and template argument deduction rules. This difference is too subtle to be of use as a general programming tool, but it is sufficient to enable the auto_ptr class to work correctly.
Don't be surprised if your compiler doesn't support the distinction between nonconstant and constant auto_ptrs yet. And be aware that if your compiler does not yet implement this distinction, your auto_ptr interface is more dangerous. In this case, it is rather easy to transfer ownership by accident.
Dietmar answered to the following question in the news:
> On p55 of Josuttis' C++ Std
> An ordinary copy constructor can copy
> an rvalue, but to do so it must declare
> its parameter as a reference to a const object.
> The alternative is to find a mechanism to
> enable an rvalue to be converted to an lvalue
> Thus the auto_ptr_ref class was introduced to
> provide this convert-to-lvalue mechanism.
It is important to track down the requirements for 'auto_ptr' to understand what is going to be achieved. Basically, the goal is to disallow copying of constant 'auto_ptr's because this would transfer ownership of the pointer to the copy but allowing to return an 'auto_ptr' from a function which requires copying of a temporary.
First check the copy ctors to determine whether a copy ctor suffices 'auto_ptr(auto_ptr const&)' cannot be used because it would allow copying of constant 'auto_ptr's and 'auto_ptr(auto_ptr&)' cannot be used because it cannot copy a temporary object. Since copy ctors only work with references (otherwise an infinite recursion to copy the argument would be the result) copy ctors are not sufficient.
Since it is not possible to copy an 'auto_ptr' directly, a different approach has to be taken when returning an 'auto_ptr' from a function. When returning a 'auto_ptr' from a function, the compiler finds that there is no suitable ctor to copy construct the returned object. But there is conversion to 'auto_ptr_ref' and ctor taking an 'auto_ptr_ref' constructing an 'auto_ptr'. Thus, the compiler creates an 'auto_ptr_ref' which basically just holds a reference to the original 'auto_ptr' and then constructs an 'auto_ptr' from this object. That's all (well, when returning an object, the compiler goes through this process normally twice because the returned value is copied somewhere but this does not change the process).
Since the conversion from 'auto_ptr' to 'auto_ptr_ref' is a non-const operation, a constant 'auto_ptr' can still not be copied, eg. when passing it to a function taking the argument by value.
A simple approach to track what is going on is to create a similar class and just write messages in the various ctors and conversion operations.
You can find another helpful discussion of this topic on Scott Meyers book site at:
Home of the C++ Library book