$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: Tylo (tylo_at_[hidden])
Date: 2004-08-05 12:15:33
shared_ptr improvement proposal
by T. Lovset
---
note: Parts of this may be long-winded and possible obvious to some of you,
but I feel it is needed to support the arguments. Let me know what you think.
---
I propose a modification to the smart pointer classes. In my opinion, this
modification improves the current smart pointer usage in several respects,
although it is not completely backward compatible.
However, that is also the main purpose of the modification, because as we
know, the explicit constructors that take a raw pointer is dangerous in
numerous ways. As an illustration, I show a realistic example that violates
the smart pointer preconditions in a subtle (and dangerous) manner.
As a background, some quotes from the boost "Proposal to Add General Purpose
Smart Pointers to the Library Technical Report"
(http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html):
(1)
> A common concern, dating back to [Colvin94], is that it is very easy to
violate the preconditions
> of smart pointer operations. This proposal attempts to systematically
eliminate undefined behavior
> possibilities as long as the user is manipulating already valid shared_ptr
(and weak_ptr)
> instances, but using the pointer constructor correctly is still up to the
user.
And:
> - Widespread existing practice strongly favors the unprotected but powerful
pointer constructor
> approach.
(2)
> A. General Principles
> 1. "As Close to Raw Pointers as Possible, but no Closer"
For auto_ptr and shared_ptr, we are familiar with the form:
shared_ptr<Widget> smartWidgetPtr( new Widget(a, b) );
The constructor is made "explicit" in order to avoid implicit and
unintentional construction of smart pointers, e.g.:
void f( shared_ptr<Widget> const & wp );
would without explicit constructors accept f(rawWidgetPtr) with disastrous
results. Still, as mentioned, "explicit" does not make up for all misuses. For
example, an inexperienced user could try to call f(shared_ptr<Widget>
(rawWidgetPtr)) to make it compile. Fortunately, this is quite easy to spot as
an error, and therefore "explicit" does it's mission here.
But a much more common and subtle problem may arise when converting from raw
pointers to smart pointers:
class WidgetUtil {
public:
WidgetUtil(Widget* wp);
private:
Widget* m_wp;
};
WidgetUtil::WidgetUtil(Widget* wp)
: m_wp(wp) { ... }
Now converting to shared_ptr:
class WidgetUtil {
public:
WidgetUtil(Widget* wp);
private:
shared_ptr<Widget> m_wp;
};
The unmodified constructor still compiles, but the precondition for m_wp is
violated. One must therefore pay close attention to the constructors to make
sure that they do not take raw pointer as arguments, which initializes smart
pointers.
In the case of a big class with a mixture of both smart and raw pointer
members, the principle of "using the pointer constructor correctly is still up
to the user", lays an unacceptable heavy burden upon the user (in my opinion) -
especially when it is possible to avoid this problem.
Before I move on to the proposal, I'll address another drawback with the
current smart pointer implementations: implicit ctor-initialization and
assignment to NULL (which is possible with raw pointers) is not supported.
This would make things much easier when converting back and forth between
smart and raw pointers. It also breaks the principle of "As Close to Raw
Pointers as Possible, but no Closer". My proposal makes this possible, and
thus making the usage closer to that of raw pointers, without introducing any
side effects.
[As a side note, Joe Guttman pointed out to me that with the proposed nullptr
and nullptr_t, it will be easy to add these features to the current
implementations of smart pointers. However, my proposal does not rely on
nullptr.]
The Proposal
============
Note that this proposal would be beneficial to most smart pointer
implementation, including shared_ptr and auto_ptr. Unfortunately, auto_ptr is
already in the standard, so it can't be touched.
I propose this usage (for shared_ptr):
shared_ptr<Widget> wp1 = NULL;
shared_ptr<Widget> wp2 = rawptr(new Widget(a, b));
wp1 = rawptr( new Widget(a, b) );
wp2 = NULL;
Current shared_ptr usage:
shared_ptr<Widget> wp1( NULL );
shared_ptr<Widget> wp2( new Widget(a, b) );
wp1.reset( new Widget(a, b) );
wp2.reset();
Discussion:
1. As wp1 and wp2 should be raw pointer "look alikes", the proposed usage is
more intuitive than the current usage, in my opinion, and allows to assign
NULL without side-effects.
2. As noted by others [e.g. Andrei Alexandrescu], member functions in smart
pointers should be avoided if possible. The reset() function makes it hard to
switch between raw and smart pointers, and also allows mix-ups: wp1.reset(),
(*wp1).reset(), wp1->reset().
3. The rawptr() function makes initialization and assignment explicit using an
alternative and better syntax, IMO. E.g., you can verify that every shared_ptr
owns a valid raw pointer by investigating instances of "rawptr(" in the code.
Typically, you want to see: rawptr(new SomeClass(..)). To do this is not easy
with the current implementation. You have to search for:
a) "shared_ptr<" stack variable constructions of which takes raw pointers
as arguments.
b) ".reset(" for re-assigning a new raw pointer. Unfortunately reset() is a
commonly used name.
c) shared_ptr's in class member initiation lists, as mentioned in the
example presented.
4. Most importantly, all raw pointers that are assigned to smart pointers must
go through the rawptr() function (or rawptr_t<T> type), always making
it "explicit". The problem in the example presented earlier, is therefore
eliminated.
5. This proposal introduces no additional overhead, as long as the compiler
really inline the rawptr() function (the operator new call is dominant by an
order of magnitude in any case).
6. The only obvious problem I see, is the backward compability issue, although
it still support the "unprotected but powerful pointer constructor approach",
with a slightly different syntax.
Implementation for shared_ptr
=============================
rawptr.hpp (new file):
---------------------
namespace boost {
template <class T> class rawptr_t {
public:
explicit rawptr_t(T * p) : px(p) {}
T* get() const { return px; }
private:
rawptr_t& operator=(const rawptr_t&);
T* px;
};
template <class T> inline rawptr_t<T> rawptr(T * p) {
return rawptr_t<T>(p);
}
}
shared_ptr.hpp (diff from boost 1.31.0):
----------------------------------------
***************
*** 109,114 ****
--- 123,129 ----
// Borland 5.5.1 specific workaround
typedef shared_ptr<T> this_type;
+ struct null_tag {};
public:
***************
*** 117,130 ****
typedef T * pointer;
typedef typename detail::shared_ptr_traits<T>::reference reference;
! shared_ptr(): px(0), pn() // never throws in 1.30+
{
}
template<class Y>
! explicit shared_ptr(Y * p): px(p), pn(p, checked_deleter<Y>()) // Y must
be complete
{
! detail::sp_enable_shared_from_this(p, p, pn);
}
//
--- 132,145 ----
typedef T * pointer;
typedef typename detail::shared_ptr_traits<T>::reference reference;
! shared_ptr(null_tag* = NULL): px(0), pn() // never throws in 1.30+
{
}
template<class Y>
! shared_ptr(rawptr_t<Y> const & w): px(w.get()), pn(w.get(),
checked_deleter<Y>()) // Y must be complete
{
! detail::sp_enable_shared_from_this(w.get(), w.get(), pn);
}
//
***************
*** 226,231 ****
--- 241,258 ----
}
#endif
+ shared_ptr & operator=(null_tag *)
+ {
+ reset();
+ return *this;
+ }
+
+ template<class Y>
+ shared_ptr & operator=(rawptr_t<Y> const & w)
+ {
+ this_type(w).swap(*this);
+ return *this;
+ }
void reset() // never throws in 1.30+
{
***************
*** 235,241 ****
template<class Y> void reset(Y * p) // Y must be complete
{
BOOST_ASSERT(p == 0 || p != px); // catch self-reset errors
! this_type(p).swap(*this);
}
template<class Y, class D> void reset(Y * p, D d)
--- 262,268 ----
template<class Y> void reset(Y * p) // Y must be complete
{
BOOST_ASSERT(p == 0 || p != px); // catch self-reset errors
! this_type(rawptr(p)).swap(*this);
}
template<class Y, class D> void reset(Y * p, D d)
------------------------------------------------------------
Få din egen @start.no-adresse gratis på http://www.start.no/