Re: ANN: AutoNewPtr (oh yes, yet another smartpointer)
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* 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:
[snip]
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).
I provide the static_cast family. With library components, there is no
such thing as premature optimization since you cannot benchmark
applications yet to be written. In my opinion, it is the job of the
client code programmer to make wise choices; it is not the job of the
library implementor to decide which performance priorities client code
should have.
Oh, I don't think that argument holds. If so then we wouldn't use
private: and such to restrict client code... There is a trade-off.
With "private", I can see a trade-off since it marks what is interface and
what is implementation. I fail to see the trade-off with regard to
static_pointer_cast. If you already have dynamic_pointer_cast in the
interface, I don't see the flexibility (or what else) you gain from not
offering static_pointer_cast.
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.
That's an interesting point. I don't do GUIs (or any other event driven
software). I guess that is why it never ocured to me.
Yes, and using this technique for files etc. is not common, in fact
I've never seen it done except experimentally by myself.
And I think the reason for that is not that it's ungood idea. On the
contrary I think (still! :-) but haven't yet really tried it out)
that it's a great idea. I think the reason it's not done is that so
far the basic infra-structure for the technique, namely pointers like
CheckingPtr and AutoNewPtr, has not been available, and so the cost of
doing self-destruction instead of zombie-state has been very high.
Now, if my pointers should be successful, and/or others implement such
pointers, with the infra-structure in place I think the no-zombies way
of programming could start to take off -- perhaps, hopefully...
I am not sure what you mean by objects in zombie-state, but it appears that
you are just shifting the zombie-state from the pointee to the pointer
(which in your implementation turns "void" when the pointee decides to
self-destruct). This probably still has the advantage that it channels a
possible variety of zombie-states into a single distinguished state so that
client code has to deal with only one special case.
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)?
It's actually not all that hard. For concreteness, let us call that smart
pointer gc_ptr<T>. Upon construction of a gc_ptr<T> object ptr, space for
the pointee is allocated and ptr knows which memory region it owns. In
particular, it can register in a static database. Now, the pointee is
constructed. If T has member objects of type gc_ptr<X>, those will be
constructed in the process. Each of those will check where it resides in
memory and then notify ptr that a gc_ptr object has been constructed in
the memory region owned by ptr. This information is stored and used for a
simple garbage collector. Finally, you can remove the region from the
static database to save space.
Huh, I'm not sure I understood that.
It sounds like a strict hierarchy.
But with a strict hierarchy you don't have cycles?
Let me try again with code. I use a mark-sweep collector. In the mark phase,
you need to find all reachable objects. To do that, you need to know the
pointers inside a pointee (that is why compiler support would come in
handy). The following is about gathering that kind of data: upon
construction of the pointee, all its gc_ptr members are registered.
#include <memory>
#include <utility>
#include <map>
#include <vector>
#include <new>
#include <iostream>
#include <functional>
#include <iterator>
class gc_ptr_base {
std::vector< gc_ptr_base * > the_children;
void register_child ( gc_ptr_base * child ) {
the_children.push_back( child );
}
typedef std::pair< char const *, char const * > range;
typedef std::map< gc_ptr_base *, range > range_table;
static
range_table the_table;
static
bool is_in_range ( char const * c_ptr,
range const & the_range ) {
if ( std::less< char const * >()( c_ptr, the_range.first ) ) {
return ( false );
}
return ( std::less< char const * >()( c_ptr, the_range.second ) );
}
public:
gc_ptr_base ( void ) {
char const * where =
static_cast<char const *>( static_cast<void *>( this ) );
for ( range_table::iterator iter = the_table.begin();
iter != the_table.end(); ++iter ) {
if ( is_in_range( where, iter->second ) ) {
iter->first->register_child( this );
}
}
}
void insert ( char const * from, char const * to ) {
the_table[ this ] = range( from, to );
}
void erase ( void ) {
the_table.erase( this );
}
void print_children ( std::ostream & o_str ) const {
std::copy( the_children.begin(), the_children.end(),
std::ostream_iterator< gc_ptr_base * >( o_str, " " ) );
}
};
gc_ptr_base::range_table gc_ptr_base::the_table;
struct nullptr_t {};
static nullptr_t nullptr;
template < typename T, typename A = std::allocator<T> >
class gc_ptr : public gc_ptr_base {
A the_allocator;
T * the_pointer;
public:
gc_ptr ( A a = A() )
: gc_ptr_base ()
, the_allocator( a )
, the_pointer ( the_allocator.allocate( 1 ) )
{
char const * data_ptr =
static_cast< char const * >( static_cast<void*>( the_pointer ) );
insert( data_ptr, data_ptr + sizeof(T) );
new (the_pointer) T ();
erase();
}
gc_ptr ( nullptr_t, A a = A() )
: gc_ptr_base ()
, the_allocator ( a )
, the_pointer ()
{}
T * get ( void ) const {
return ( the_pointer );
}
};
struct X {
gc_ptr<X> prev;
gc_ptr<X> next;
X ( void )
: prev( nullptr )
, next( nullptr )
{}
};
int main ( void ) {
gc_ptr<X> test;
std::cout << "pointee: " << test.get() << '\n'
<< "pointer members: ";
test.print_children( std::cout );
std::cout << '\n';
}
The code does not show anything beyond the registration phase. Note, how the
constructor of gc_ptr does something before and something after the pointee
is constructed. That is why encapsulating construction inside the smart
pointer is useful in this context.
[snip]
Best
Kai-Uwe Bux