Re: ANN: AutoNewPtr (oh yes, yet another smartpointer)
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
[snip]
I just coded this up, and hopefully by mentioning it here I'll get some
useful feedback, like, bugs, shortcomings, improvements...
http://code.google.com/p/alfps/source/browse/trunk/alfs/pointers/AutoNewPtr.hpp
[snip]
When I did something along these lines (a smart pointer that also
encapsulates allocation), I made a few different choices. Just for
comparison:
a) Instead of a custom deleter
Well, just so as not to mislead readers, there isn't a dynamic custom
deleter, only possible specialization of std::default_delete.
, I templated the class on an allocator and
allowed for allocator objects to be passed upon construction. The
allocator is in charge of allocation and deallocation of the pointee.
Yes. I haven't addressed the issue of customized creation. Not sure
if it's really useful, considering that raw allocation can be
customized via operator new and operator delete?
With shared_ptr a custom per-instance deleter is necessary because the
pointee is supplied externally, so could come from anywhere, e.g. from
some factory function, or being a pointer to literal string in one
invocation and a pointer to dynamically allocated string in other
invocation. Even though, for pointer to literal, shared_ptr doesn't
really support "no deletion" because it adds a lot of overhead. That
extreme shared_ptr overhead for the simplest case is one reason why I
think it's a shame that intrusive_ptr is not in the C++0x proposal.
And as illustrated by CheckingPtr and AutoNewPtr, intrusive_ptr is
really great in general for building one's own smart shared pointers.
I think, if I had to choose between shared_ptr or intrusive_ptr in the
standard, I'd choose the latter (but best, of course, to have both!).
Because the most relevant functionality of shared_ptr is easily
implemented in terms of intrusive_ptr, whereas the opposite is
practically impossible: one can't get rid of shared_ptr inefficiency.
b) The default constructor would work like the other forwarding
constructors and construct a pointer with default constructed pointee.
That was my original inclination, for consistency, but after some
reflection, IMO it limits the usefulness by prohibiting using the
pointer type with e.g. templated code that default-constructs
nullpointers (which includes using arrays of pointers).
c) To create a null pointer equivalent, a constructor
smart_pointer ( nullptr_t )
was provided. (I also provided comparison functions with nullptr_t
arguments.)
I think that's the same point as (b). :-)
d) I more or less followed tr1::shared_ptr with regard to the interface
(other than the allocator stuff). Thus, dynamic casting is provided by
means of free standing friend functions.
Why?
On a related note, are your
pointers covariant with respect to derived classes? i.e., if D derives
from
T, is there an implicit conversion from smart_pointer<D> to
smart_pointer<T>? I did not see the trickery I usually use for that in
your
code (and I would not even know how to make that work with the wrappers
that you create to make the deletion notifications work).
Yes, there is covariant conversion. It's quite simple; I don't
understand what you mean by "trickery". All conversions are done by
constructors, so in CheckingPtr that conversion is performed by
// Logical "implicit upcast".
template< typename U >
CheckingPtr( CheckingPtr<U> const& other )
: myPtr( other.myPtr )
, myDeleter( other.myDeleter )
{}
Here the static type checking is imposed by the initialization of
myPtr with other.myPtr.
I haven't implemented static_cast-like functionality for going the
other way, but there is dynamic_cast-like functionality for that (IMHO
static_cast-like functionality would, at this level, just be a risky
optimization, and providing it could be evil premature optimization).
e) Instead of a deletion notification framework that is pointee based, I
experimented with a pointer that has a kill() member function to
delete the
pointee and notify all interested parties.
One reason that I chose to just /detect/ pointee destruction is that
if the pointee is an object in some GUI framework, you can't easily
get the GUI framework to call a kill() function: it will just destroy
the pointee (e.g. when parent window closes or user closes a window).
Another reason is that for self-destruction as an alternative to
zombie state, e.g. for a file object, with a kill() function the
pointee would have to know about the smart pointer and sort of "reach
up" to the smart pointer's kill() function to destroy itself.
As it is, with just detection of destruction, it's all largely
transparent.
BTW: I also used encapsulated construction among, to implement a
garbage-collecting smart pointer that would work transparently with
cycles.
Unfortunately, there is a considerable overhead.
That sounds interesting. I know there is a proposal for helping out
with collection of cycles. It sounds like your approach is/was quite
different (even though I don't recollect the details of the proposal)?
I haven't addressed the issue of cycles at all.
A first step in that address would be something equivalent to
boost::weak_ptr, but, I haven't yet needed such equivalent... :-)
Cheers, &
thanks for your comments & insights,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?