Topic: Defect Report: auto_ptr usability issues
Author: Rani Sharoni <rani_sharoni@hotmail.com>
Date: Sun, 7 Dec 2003 16:28:17 +0000 (UTC) Raw View
[ Note: Forwarded to C++ Committee. -sdc]
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://anubis.dkuug.dk/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:
1) call auto_ptr<B>::operator auto_ptr_ref<B>()
2) call string::string(char const*) and throw
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).
Thanks,
Rani
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html ]