auto_ptr
usability issuesSection: 99 [auto.ptr] Status: NAD Submitter: Rani Sharoni Opened: 2003-12-07 Last modified: 2016-01-28
Priority: Not Prioritized
View all other issues in [auto.ptr].
View all issues with NAD status.
Discussion:
TC1 CWG DR #84 effectively made the template<class Y> operator auto_ptr<Y>()
member of auto_ptr
(20.4.5.3/4) obsolete.
The sole purpose of this obsolete conversion member is to enable copy initialization base from r-value derived (or any convertible types like cv-types) case:
#include <memory> using std::auto_ptr; struct B {}; struct D : B {}; auto_ptr<D> source(); int sink(auto_ptr<B>); int x1 = sink( source() ); // #1 EDG - no suitable copy constructor
The excellent analysis of conversion operations that was given in the final
auto_ptr
proposal
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1997/N1128.pdf)
explicitly specifies this case analysis (case 4). DR #84 makes the analysis
wrong and actually comes to forbid the loophole that was exploited by the
auto_ptr
designers.
I didn't encounter any compliant compiler (e.g. EDG, GCC, BCC and VC) that ever allowed this case. This is probably because it requires 3 user defined conversions and in fact current compilers conform to DR #84.
I was surprised to discover that the obsolete conversion member actually has negative impact of the copy initialization base from l-value derived case:
auto_ptr<D> dp; int x2 = sink(dp); // #2 EDG - more than one user-defined conversion applies
I'm sure that the original intention was allowing this initialization using
the template<class Y> auto_ptr(auto_ptr<Y>& a)
constructor (20.4.5.1/4) but
since in this copy initialization it's merely user defined conversion (UDC)
and the obsolete conversion member is UDC with the same rank (for the early
overloading stage) there is an ambiguity between them.
Removing the obsolete member will have impact on code that explicitly invokes it:
int y = sink(source().operator auto_ptr<B>());
IMHO no one ever wrote such awkward code and the reasonable workaround for #1 is:
int y = sink( auto_ptr<B>(source()) );
I was even more surprised to find out that after removing the obsolete
conversion member the initialization was still ill-formed:
int x3 = sink(dp); // #3 EDG - no suitable copy constructor
This copy initialization semantically requires copy constructor which means
that both template conversion constructor and the auto_ptr_ref
conversion
member (20.4.5.3/3) are required which is what was explicitly forbidden in
DR #84. This is a bit amusing case in which removing ambiguity results with
no candidates.
I also found exception safety issue with auto_ptr
related to auto_ptr_ref
:
int f(auto_ptr<B>, std::string); auto_ptr<B> source2(); // string constructor throws while auto_ptr_ref // "holds" the pointer int x4 = f(source2(), "xyz"); // #4
The theoretic execution sequence that will cause a leak:
According to 20.4.5.3/3 and 20.4.5/2 the auto_ptr_ref
conversion member
returns auto_ptr_ref<Y>
that holds *this
and this is another defect since
the type of *this
is auto_ptr<X>
where X
might
be different from Y
. Several library vendors (e.g. SGI) implement
auto_ptr_ref<Y>
with Y*
as member which
is much more reasonable. Other vendor implemented auto_ptr_ref
as
defectively required and it results with awkward and catastrophic code:
int oops = sink(auto_ptr<B>(source())); // warning recursive on all control paths
Dave Abrahams noticed that there is no specification saying that
auto_ptr_ref
copy constructor can't throw.
My proposal comes to solve all the above issues and significantly simplify
auto_ptr
implementation. One of the fundamental requirements from
auto_ptr
is that it can be constructed in an intuitive manner (i.e.
like ordinary pointers) but with strict ownership semantics which yield that source
auto_ptr
in initialization must be non-const. My idea is to add additional
constructor template with sole propose to generate ill-formed, diagnostic
required, instance for const auto_ptr arguments during instantiation of
declaration. This special constructor will not be instantiated for other
types which is achievable using 14.8.2/2 (SFINAE). Having this constructor
in hand makes the constructor template<class Y> auto_ptr(auto_ptr<Y> const&)
legitimate since the actual argument can't be const yet non const r-value
are acceptable.
This implementation technique makes the "private auxiliary class"
auto_ptr_ref
obsolete and I found out that modern C++ compilers (e.g. EDG,
GCC and VC) consume the new implementation as expected and allow all
intuitive initialization and assignment cases while rejecting illegal cases
that involve const auto_ptr
arguments.
The proposed auto_ptr interface:
namespace std { 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> const&) throw(); auto_ptr& operator=(auto_ptr&) throw(); template<class Y> auto_ptr& operator=(auto_ptr<Y>) 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(); private: template<class U> auto_ptr(U& rhs, typename unspecified_error_on_const_auto_ptr<U>::type = 0); }; }
One compliant technique to implement the unspecified_error_on_const_auto_ptr
helper class is using additional private auto_ptr
member class template like
the following:
template<typename T> struct unspecified_error_on_const_auto_ptr; template<typename T> struct unspecified_error_on_const_auto_ptr<auto_ptr<T> const> { typedef typename auto_ptr<T>::const_auto_ptr_is_not_allowed type; };
There are other techniques to implement this helper class that might work better for different compliers (i.e. better diagnostics) and therefore I suggest defining its semantic behavior without mandating any specific implementation. IMO, and I didn't found any compiler that thinks otherwise, 14.7.1/5 doesn't theoretically defeat the suggested technique but I suggest verifying this with core language experts.
Further changes in standard text:
Remove section 20.4.5.3
Change 20.4.5/2 to read something like:
Initializing auto_ptr<X>
from const auto_ptr<Y>
will result with unspecified
ill-formed declaration that will require unspecified diagnostic.
Change 20.4.5.1/4,5,6 to read:
template<class Y> auto_ptr(auto_ptr<Y> const& a) throw();
4 Requires: Y*
can be implicitly converted to X*
.
5 Effects: Calls const_cast<auto_ptr<Y>&>(a).release()
.
6 Postconditions: *this
holds the pointer returned from a.release()
.
Change 20.4.5.1/10
template<class Y> auto_ptr& operator=(auto_ptr<Y> a) throw();
10 Requires: Y*
can be implicitly converted to X*
. The expression delete
get()
is well formed.
LWG TC DR #127 is obsolete.
Notice that the copy constructor and copy assignment operator should remain
as before and accept non-const auto_ptr&
since they have effect on the form
of the implicitly declared copy constructor and copy assignment operator of
class that contains auto_ptr as member per 12.8/5,10:
struct X { // implicit X(X&) // implicit X& operator=(X&) auto_ptr<D> aptr_; };
In most cases this indicates about sloppy programming but preserves the
current auto_ptr
behavior.
Dave Abrahams encouraged me to suggest fallback implementation in case that
my suggestion that involves removing of auto_ptr_ref
will not be accepted.
In this case removing the obsolete conversion member to auto_ptr<Y>
and
20.4.5.3/4,5 is still required in order to eliminate ambiguity in legal
cases. The two constructors that I suggested will co exist with the current
members but will make auto_ptr_ref
obsolete in initialization contexts.
auto_ptr_ref
will be effective in assignment contexts as suggested in DR
#127 and I can't see any serious exception safety issues in those cases
(although it's possible to synthesize such). auto_ptr_ref<X>
semantics will
have to be revised to say that it strictly holds pointer of type X
and not
reference to an auto_ptr
for the favor of cases in which auto_ptr_ref<Y>
is
constructed from auto_ptr<X>
in which X
is different from
Y
(i.e. assignment from r-value derived to base).
[Redmond: punt for the moment. We haven't decided yet whether we want to fix auto_ptr for C++-0x, or remove it and replace it with move_ptr and unique_ptr.]
[
Oxford 2007: Recommend NAD. We're just going to deprecate it. It still works for simple use cases
and people know how to deal with it. Going forward unique_ptr
is the recommended
tool.
]
[ 2007-11-09: Reopened at the request of David Abrahams, Alisdair Meredith and Gabriel Dos Reis. ]
[ 2009-07 Frankfurt ]
This is a complicated issue, so we agreed to defer discussion until later in the week so that interested parties can read up on it.
[ 2009-10-04 Daniel adds: ]
I suggest to close this issue as NAD. The reasons are two-fold: First, the suggested proposed resolution uses no longer appropriate language means to solve this issue, which has the effect that the recommended resolution is another - but better - form of hack. Second, either following the suggested resolution or the now more natural alternative via the added member set
template<class Y> auto_ptr(auto_ptr<Y>&&) throw(); template<class Y> auto_ptr& operator=(auto_ptr<Y>&&) throw();would still have a non-zero probability to break user-code that actively references
auto_ptr_ref
. This risk seems to indicate that a decision which would not touch the current spec ofauto_ptr
at all (but deprecating it) and instead recommending to useunique_ptr
for new code instead might have the best cost-benefit ratio. IMO the current solution of 1100 can be considered as an active user-support for this transition.
[ 2009-10 Santa Cruz: ]
Mark as NAD. Alisdair will open a new issue (1247) with proposed wording to handle
auto_ptr_ref
.
Proposed resolution:
Change the synopsis in 99 [auto.ptr]:
namespace std {template <class Y> struct auto_ptr_ref {};// exposition only template <class T> struct constant_object; // exposition only template <class T> struct cannot_transfer_ownership_from : constant_object<T> {}; template <class X> class auto_ptr { public: typedef X element_type; // D.9.1.1 construct/copy/destroy: explicit auto_ptr(X* p =0) throw(); auto_ptr(auto_ptr&) throw(); template<class Y> auto_ptr(auto_ptr<Y> const&) 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(); // D.9.1.2 members: X& operator*() const throw(); X* operator->() const throw(); X* get() const throw(); X* release() throw(); void reset(X* p =0) throw();// D.9.1.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();// exposition only template<class U> auto_ptr(U& rhs, typename cannot_transfer_ownership_from<U>::error = 0); }; template <> class auto_ptr<void> { public: typedef void element_type; }; }
Remove 99 [auto.ptr.conv].
Change 99 [auto.ptr], p3:
The
auto_ptr
provides a semantics of strict ownership. Anauto_ptr
owns the object it holds a pointer to. Copying anauto_ptr
copies the pointer and transfers ownership to the destination. If more than oneauto_ptr
owns the same object at the same time the behavior of the program is undefined. Templatesconstant_object
andcannot_transfer_ownership_from
, and the final constructor ofauto_ptr
are for exposition only. For any typesX
andY
, initializingauto_ptr<X>
fromconst auto_ptr<Y>
is ill-formed, diagnostic required. [Note: The uses ofauto_ptr
include providing temporary exception-safety for dynamically allocated memory, passing ownership of dynamically allocated memory to a function, and returning dynamically allocated memory from a function.auto_ptr
does not meet theCopyConstructible
andAssignable
requirements for Standard Library container elements and thus instantiating a Standard Library container with anauto_ptr
results in undefined behavior. -- end note]
Change [auto.ptr.cons], p5:
template<class Y> auto_ptr(auto_ptr<Y> const& a) throw();Requires:
Y*
can be implicitly converted toX*
.Effects: Calls
const_cast<auto_ptr<Y>&>(
a
)
.release()
.Postconditions:
*this
holds the pointer returned froma.release()
.
Change [auto.ptr.cons], p10:
template<class Y> auto_ptr& operator=(auto_ptr<Y>&a) throw();Requires:
Y*
can be implicitly converted toX*
. The expressiondelete get()
is well formed.Effects: Calls
reset(a.release())
.Returns:
*this
.