Making a smart pointer which works with incomplete types

J

Juha Nieminen

I asked a long time ago in this group how to make a smart pointer
which works with incomplete types. I got this answer (only relevant
parts included):

//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
Data_t* data;
void(*deleterFunc)(Data_t*);

static void deleter(Data_t* d) { delete d; }

void decrementReferenceCount()
{
if(data)
{
// decrement reference count and then:
if(<reference count == 0>)
{
deleterFunc(data);
}
}
}

public:
SmartPointer(Data_t* d): data(d), deleterFunc(&deleter) {}
~SmartPointer() { decrementReferenceCount(); }
// etc...
};
//------------------------------------------------------------------

When done like this, the template type must be complete only when the
smart pointer is constructed. It doesn't need to be complete when it's
destructed, copied or assigned.

What bothered me back then is that the pointer to the deleter function
is a member variable of the class, increasing its size. What is worse,
all the smart pointer instances of the same type will have the exact
same function pointer as member variable, which feels like a huge waste.

Then it occurred to me: Is there any reason this pointer cannot be
static? Like this:

//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
....
static void(*deleterFunc)(Data_t*);
....
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
....
};

template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------

This way the pointer to the deleter will be stored in the program only
once, and most importantly it will not increment the size of the smart
pointer.

This feels so glaringly obvious to me now that I really wonder why
this wasn't suggested to me to begin with. Is there some error here I'm
missing?

(It might seem hazardous to have the function pointer initialized to
null, as it might happen that the decremetReferenceCount() function
might in some situations make a call to that null pointer. However,
whenever there is something in the 'data' pointer, the deleter function
pointer will always have been initialized. It's no different in this
regard as having the function pointer as a member variable.)
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux:
Alf said:
* Juha Nieminen:

Then it occurred to me: Is there any reason this pointer cannot be
static? Like this:

//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
...
static void(*deleterFunc)(Data_t*);
...
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
...
};

template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------

This way the pointer to the deleter will be stored in the program
only
once, and most importantly it will not increment the size of the smart
pointer.

This feels so glaringly obvious to me now that I really wonder why
this wasn't suggested to me to begin with. Is there some error here I'm
missing?
Yeah. Successive smart pointer instantiations can change the common
deleter func pointer.
[snip]

How?

Sorry, I reacted to the static pointer. As I wrote use a template
parameter instead. The only valid reason for having a pointer to that
function is to change the pointer, and that's apparently done in the
constructor body,

Really? Then a smart pointer working with incomplete types (in the sense
above) either does not qualify as a valid reason or there must be a way
without that (static) pointer. Which is it? and if it is the second, which
way of making a smart pointer work with incomplete types do you have in
mind?
so I read the constructor argument as pointer to func
(didn't really read it, I only inferred the only thing that could make
sense, assuming the code was sensible).

As I wrote, use a template parameter.

The static pointer doesn't make sense, and having a nonsensical thing in
there causes people like me to draw incorrect conclusions about the code
(we don't actually read it, we just look at it, like e.g. someone good at
chess might look at a randomly generated chess position and assume that
it's sensible and draw wrong conclusions).

As long as the issue about incomplete types is not out of the way, I will
defer judgement as to whether the code presented is nonsensical or not.


Best

Kai-Uwe Bux
 
L

Lance Diduck

  This feels so glaringly obvious to me now that I really wonder why
this wasn't suggested to me to begin with. Is there some error here I'm
missing?
Most shared_ptr implementations will use ECBO for the deleter. i.e.
template<class T,class D>
struct shared_ptr_imp:D{
T* data;
unsigned refcount;
void decrementReferenceCount()
{
if(data)
{
// decrement reference count and then:
if(<refcount == 0>)
{
this->D::eek:perator()(data);//exapanded to see whats
really happening
}
}
}
};
In the vast majority of cases, D is something like
template<class T>
struct DeleteMe{
void operator()(T* d){delete d;}
};//DeleteMe has no data, therefore ECBO add no storage
Using ECBO, you will find this
sizeof(shared_ptr_imp<T,DeleteMe>)==(sizeof(T*)+sizeof(unsigned));//
ignoring padding etc

Only in the rare cases where the deleter actually has data you would
see an increase in size, i.e.
template<class T,class U>
struct Alias{//keep shared_ptr<U> alive as long as shared_ptr<T>
exists
void operator()(T* d){}
Alias(shared_ptr<U> const&k):m_keep(k){}
private:
shared_ptr<U> m_keep;
};//shared_ptr alias that preserves the ordering invaraints

Lance
 
J

Juha Nieminen

Alf said:
Yeah. Successive smart pointer instantiations can change the common
deleter func pointer.

They can, but they won't. The deleter function pointer is private to
the smart pointer class, so only the smart pointer itself can use it.
The pointer is also type-specific, so a new function pointer is created
by the compiler for each type with which the smart pointer is used.

If the only thing you have is the smart pointer constructor setting
the static deleter function pointer, what else can it point to besides
the deleter for the type? Nothing else.
Instead make the deleter function a template parameter.

What for? If you make it a mandatory template parameter that's very
inconvenient for the user because he would have to write an explicit
deleter for each single type he uses the smart pointer with. If the
template parameter is optional, it would require a default value. What
would be this default value (and how would it be different from what I
posted)?
But even better, forget about this premature optimization.

"Don't optimize prematurely" does not mean "deliberately make
inefficient code that hogs memory for no good reason". If making a more
efficient smart pointer is trivial and safe, I see absolutely no
compelling reason to not to do so. Why would I *deliberately* make the
smart pointer larger than necessary, if there is absolutely no reason to
do so?
For the few cases where this functionality is needed, e.g. PIMPL, just
use a boost::shared_ptr.

boost::shared_ptr is a horrendously inefficient memory hog. For
example, in a typical 32-bit linux system each instance of
boost::shared_ptr (which doesn't share the allocated object) consumes 56
bytes of memory, and all of its operations are slow (for example because
it allocates dynamically the payload data, and all of its operations are
thread-safe, even if no multithreading is done).

"56 bytes? No big deal." It indeed isn't a big deal if the number of
shared_ptr instances is relatively low compared to the total amount of
data used in the program, and its runtime overhead isn't either a big
deal if the shared_ptr instances are not created/destroyed/copied around
a lot, but only used to access the data they point to. Sure.

However, if you need to instantiate enormous amounts of smart pointers
and you perform a lot of copying and other operations to them, the
overhead can make a big difference in memory consumption and speed. When
you have millions of smart pointer instances, each single byte saved
counts. Whether your smart pointer consumes 56 bytes or 8 bytes can make
a difference of hundreds of megabytes of saved RAM consumption (RAM
which could be used for something more useful).

(In fact, I can't understand why boost::shared_ptr stores a deleter
function pointer in the payload it creates. I see absolutely no reason
why a static function pointer wouldn't work equally well. It would save
4/8 bytes per instance. Not much, but it's free and has no negative
consequences.)
 
J

Juha Nieminen

Alf said:
Consider the following:

template< typename T >
void destroy( T* );

template< typename T >
class SmartPointer
{
...
public:
~SmartPointer() { if( ... ) { destroy( myReferent ); } }
};

It achieves the same as the original code without any static pointer.

From the point of view of incomplete types, exactly how is that
destructor different from this one:

~SmartPointer() { if( ... ) { delete myReferent; } }

The whole idea with the deleter function pointer is that the
destructor of the smart pointer can delete the object even if the
object's type is incomplete in that context. How exactly does your
version achieve this?
For more flexibility (in the direction that a static pointer could add
flexibility) make the deleter a template parameter.

The whole idea is to be able to delete the object with only an
incomplete type. Could you explain how the template parameter helps in
this task?
 
J

Juha Nieminen

Pete said:
TR1's shared_ptr (based on Boost) puts the deleter object in the control
block

I know it does that, and I don't understand why. What for? Why
wouldn't a static function pointer be enough?
along with the reference count and the pointer to the managed
resource.

Are you sure it puts also the pointer to the managed object in the
control block, rather than it being a direct member variable of the
shared_ptr class? boost::shared_ptr has it as a member.

Putting it in the control block makes the smart pointer more
inefficient speedwise because each access to the allocated object
requires one additional indirection step (compared to the pointer being
a direct member of the smart pointer class).

Also putting it there saves memory only if more than one smart pointer
points to the same object. Else there's no memory saving.
The deleter is not part of the type (i.e. it's not a template
argument).

I know how boost::shared_ptr handles the deleter function. I just
can't understand why it does it like that. I can't think of any reason
why a static class variable wouldn't do. The advantage is that it
doesn't consume memory for each shared_ptr instance. I can't think of
any disadvantage.
 
J

Juha Nieminen

Lance said:
Most shared_ptr implementations will use ECBO for the deleter. i.e.
template<class T,class D>

In this case the user would have to always provide a deleter for the
smart pointer because it has no default value. This can be burdensome
and a nuisance. It should be made optional.
struct shared_ptr_imp:D{
T* data;
unsigned refcount;
void decrementReferenceCount()
{
if(data)
{
// decrement reference count and then:
if(<refcount == 0>)
{
this->D::eek:perator()(data);//exapanded to see whats
really happening

If D is a template class, that line instantiates the D::eek:perator()
function. If that function calls the destructor of 'data', it will
require for T to be a complete type.
}
}
}
};
In the vast majority of cases, D is something like
template<class T>
struct DeleteMe{
void operator()(T* d){delete d;}

Which is exactly the problem: DeleteMe::eek:perator() cannot properly
destroy 'd' if T is an incomplete type (it cannot call its destructor).

That's exactly what the deleter function pointer trick solves: Rather
than performing a 'delete' in the destructor of the smart pointer, what
it does is that it simply calls the deleter function through the
pointer. The actual deleter function has been instantiated and the
pointer to it assigned to the deleter function pointer variable in the
constructor. The only thing the destructor refers to is this pointer
variable and nothing else. Thus it works with incomplete types (as long
as the constructor was instantiated in a context where the type was
complete).
 
J

James Kanze

* Kai-Uwe Bux:
Alf said:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Juha Nieminen:
Then it occurred to me: Is there any reason this pointer cannot be
static? Like this:
//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
...
static void(*deleterFunc)(Data_t*);
...
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
...
};
template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------
This way the pointer to the deleter will be stored in the program
only
once, and most importantly it will not increment the size of the smart
pointer.
This feels so glaringly obvious to me now that I really wonder why
this wasn't suggested to me to begin with. Is there some error here I'm
missing?
Yeah. Successive smart pointer instantiations can change the common
deleter func pointer.
[snip]
How?
Sorry, I reacted to the static pointer. As I wrote use a template
parameter instead. The only valid reason for having a pointer to that
function is to change the pointer, and that's apparently done in the
constructor body,
Really? Then a smart pointer working with incomplete types
(in the sense above) either does not qualify as a valid
reason or there must be a way without that (static) pointer.
Which is it? and if it is the second, which way of making a
smart pointer work with incomplete types do you have in
mind?
Consider the following:
template< typename T >
void destroy( T* );
template< typename T >
class SmartPointer
{
...
public:
~SmartPointer() { if( ... ) { destroy( myReferent ); } }
};
It achieves the same as the original code without any static pointer.

No. With the original code, you can delete the pointer in a
context where the type is incomplete. with your code, you
can't.
For more flexibility (in the direction that a static pointer
could add flexibility) make the deleter a template parameter.

Which would require the type to be complete when you
instantiation the template.
 
L

Lance Diduck

  In this case the user would have to always provide a deleter for the
smart pointer because it has no default value. This can be burdensome
and a nuisance. It should be made optional.


  If D is a template class, that line instantiates the D::eek:perator()
function. If that function calls the destructor of 'data', it will
require for T to be a complete type.


  Which is exactly the problem: DeleteMe::eek:perator() cannot properly
destroy 'd' if T is an incomplete type (it cannot call its destructor).
That's exactly what the deleter function pointer trick solves: Rather
than performing a 'delete' in the destructor of the smart pointer, what
it does is that it simply calls the deleter function through the
pointer.
There has been a long standing feature in C++ called "virtual
desstructor" that exacly solves this problem in this same way.i.e.
struct Incomplete{
virtual void foo()=0;
virtual ~Incomplete(){}
};
struct Complete:Incomplete{
void foo(){}
};
Incomplete * i=new Complete;
delete i;//does not require Complete type
That last line is resolved at runtime to be:
i->~Complete();
Complete::eek:perator delete(dynamic_cast<void*>( i));

So I am unclear on why you may want to do this manually in a smart
pointer?
 
J

James Kanze

I know it does that, and I don't understand why. What for? Why
wouldn't a static function pointer be enough?

No. The whole purpose of doing this is to allow different
instances to have different deleters.
Are you sure it puts also the pointer to the managed object in
the control block, rather than it being a direct member
variable of the shared_ptr class? boost::shared_ptr has it as
a member.

I rather suspect that it's unspecified. I don't see how you
could tell from the outside.
Putting it in the control block makes the smart pointer more
inefficient speedwise because each access to the allocated
object requires one additional indirection step (compared to
the pointer being a direct member of the smart pointer class).

Putting it in the pointer object itself makes the smart pointer
more inefficient speedwise because copying it requires copying
two pointers, and not just one.

It's six of one, and half dozen of the other.
Also putting it there saves memory only if more than one smart
pointer points to the same object. Else there's no memory
saving.

Which is really the point of using shared_ptr, no? Otherwise,
why bother? If there's only one instance, scoped_ptr or
auto_ptr is far more appropriate.
I know how boost::shared_ptr handles the deleter function. I
just can't understand why it does it like that.

Because it works?
I can't think of any reason why a static class variable
wouldn't do.

Because it doesn't work?
The advantage is that it doesn't consume memory for each
shared_ptr instance. I can't think of any disadvantage.

Except that it doesn't work, there's no real disadvantage. Some
people consider this fact a serious disadvantage, however.
 
K

Kai-Uwe Bux

Alf said:
* James Kanze:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Juha Nieminen:
Then it occurred to me: Is there any reason this pointer cannot
be
static? Like this:
//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
...
static void(*deleterFunc)(Data_t*);
...
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
...
};
template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------
This way the pointer to the deleter will be stored in the program
only
once, and most importantly it will not increment the size of the
smart pointer.
This feels so glaringly obvious to me now that I really wonder
why
this wasn't suggested to me to begin with. Is there some error here
I'm missing?
Yeah. Successive smart pointer instantiations can change the common
deleter func pointer.
[snip]
How?
Sorry, I reacted to the static pointer. As I wrote use a template
parameter instead. The only valid reason for having a pointer to that
function is to change the pointer, and that's apparently done in the
constructor body,
Really? Then a smart pointer working with incomplete types
(in the sense above) either does not qualify as a valid
reason or there must be a way without that (static) pointer.
Which is it? and if it is the second, which way of making a
smart pointer work with incomplete types do you have in
mind?
Consider the following:
template< typename T >
void destroy( T* );
template< typename T >
class SmartPointer
{
...
public:
~SmartPointer() { if( ... ) { destroy( myReferent ); } }
};
It achieves the same as the original code without any static pointer.

No. With the original code, you can delete the pointer in a
context where the type is incomplete. with your code, you
can't.

Well, when you say something like that then you start me thinking if I may
have overlooked something, e.g something really basic. You force me to
actually code up a test case and check it. With at least two different
compilers.

Could you post the code? I have a little trouble deciding when and where to
put the definition for the template

template< typename T >
void destroy( T* );


Best

Kai-Uwe Bux
 
J

Juha Nieminen

Alf said:
<code file="x.cpp">
#include "sp.h"

class X;
X* newX();
void deleteX( X* );

int main()
{
SmartPtr<X, deleteX> p( newX() );
}
</code>

Here you require a "deleteX" function to be implemented by the user
alongside the X class, and you require this "deleteX" function to be
given as template parameter to the smart pointer.

Of course that works like that, but it's burdensome for the user to
have to do that for every single type he uses with the smart pointer.
The smart pointer ought to create such a function automatically to ease
the user's task, which is the whole point.
 
J

Juha Nieminen

Alf said:
A templated function.

And how do you call this templated function from the destructor of the
smart pointer class without requiring for the object type to be complete?
It may be that 56 bytes won't matter where there is a need for this
pointer.

Then by all means use boost::shared_ptr. You'll get many advantages
from it (such as it being thread-safe).

However, there may be other situations where boost::shared_ptr takes
too much memory. Thus boost::shared_ptr is not good for *all* possible
situations.

A cache read is slower than no cache read at all.
[snip]
(In fact, I can't understand why boost::shared_ptr stores a deleter
function pointer in the payload it creates. I see absolutely no reason
why a static function pointer wouldn't work equally well. It would save
4/8 bytes per instance. Not much, but it's free and has no negative
consequences.)

It's just the functionality chosen, having per-pointer deleter, which
allows having two or more shared_ptr<T> with different destruction
behavior.

The problem is that shared_ptr always uses the exact same deleter
function for all the instances of shared_ptr<T>. I can't see any reason
why the pointer to the deleter function must be stored in each instance.
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux:
Alf said:
* James Kanze:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Juha Nieminen:
Then it occurred to me: Is there any reason this pointer cannot
be
static? Like this:
//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
...
static void(*deleterFunc)(Data_t*);
...
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
...
};
template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------
This way the pointer to the deleter will be stored in the
program only
once, and most importantly it will not increment the size of the
smart pointer.
This feels so glaringly obvious to me now that I really wonder
why
this wasn't suggested to me to begin with. Is there some error
here I'm missing?
Yeah. Successive smart pointer instantiations can change the
common deleter func pointer.
[snip]
How?
Sorry, I reacted to the static pointer. As I wrote use a template
parameter instead. The only valid reason for having a pointer to
that function is to change the pointer, and that's apparently done
in the constructor body,
Really? Then a smart pointer working with incomplete types
(in the sense above) either does not qualify as a valid
reason or there must be a way without that (static) pointer.
Which is it? and if it is the second, which way of making a
smart pointer work with incomplete types do you have in
mind?
Consider the following:
template< typename T >
void destroy( T* );
template< typename T >
class SmartPointer
{
...
public:
~SmartPointer() { if( ... ) { destroy( myReferent ); } }
};
It achieves the same as the original code without any static pointer.
No. With the original code, you can delete the pointer in a
context where the type is incomplete. with your code, you
can't.
Well, when you say something like that then you start me thinking if I
may have overlooked something, e.g something really basic. You force me
to actually code up a test case and check it. With at least two
different compilers.

Could you post the code? I have a little trouble deciding when and where
to put the definition for the template

template< typename T >
void destroy( T* );

Sigh.

Following is the code for James' comments.

Although this uses template parameter (which is what I recommended for
ease of use) it is trivial to remove that feature, requiring a litte more
client code.


<code file="sp.h">
#ifndef SP_H
#define SP_H

template< typename T >
void destroy( T* );

template< typename T, void (*doDestroy)(T*) = &destroy<T> >
class SmartPtr
{
private:
T* myReferent;

SmartPtr( SmartPtr const& );
SmartPtr& operator=( SmartPtr const& );

public:
SmartPtr( T* p ): myReferent( p ) {}
~SmartPtr() { doDestroy( myReferent ); }
};

#endif
</code>


<code file="x.cpp">
#include "sp.h"

class X;
X* newX();
void deleteX( X* );

int main()
{
SmartPtr<X, deleteX> p( newX() );
}
</code>


<code file="y.cpp">
#include <iostream>
using namespace std;

class X
{
private:
X( X const& );
X& operator=( X& );
public:
X() { cout << "X::<init>" << endl; }
~X() { cout << "X::<destroy>" << endl; }
};

X* newX() { return new X; }
void deleteX( X* p ) { delete p; }
</code>

It appears that you introduce a trade-off. You eliminate the pointer at the
cost of increasing the conceptual requirements for X, in particular, a
function deleteX (or some specialization of destroy(X*)) has to exist. I
leave it up to Juha to judge whether this meets his requirements. In any
case, I think his original solution with a static pointer to a deleter has
not been demonstrated to be absurd.


Best

Kai-Uwe Bux
 
J

Juha Nieminen

James said:
Putting it in the pointer object itself makes the smart pointer
more inefficient speedwise because copying it requires copying
two pointers, and not just one.

Which operation is more likely to be more common in a typical program:
Copying a smart pointer or accessing the data it's pointing to?
Which is really the point of using shared_ptr, no? Otherwise,
why bother? If there's only one instance, scoped_ptr or
auto_ptr is far more appropriate.

Objects might be shared only temporarily (for example while sorting an
array of smart pointers, for instance), and/or the vast majority of the
smart pointers in the program might be shared, even though some of them
may be (so they must support it).

In fact, I would say that the most common situation in most programs
is for smart pointers to be unshared, and permanently (rather than
temporarily, for a very short time) abundantly-shared smart pointers are
more the exception than the rule.
Because it works?

According to my own tests, it works. I see no problems with it.
Because it doesn't work?

Exactly how does it not work?
 
J

Juha Nieminen

Lance said:
There has been a long standing feature in C++ called "virtual
desstructor" that exacly solves this problem in this same way.i.e.
struct Incomplete{
virtual void foo()=0;
virtual ~Incomplete(){}
};
struct Complete:Incomplete{
void foo(){}
};
Incomplete * i=new Complete;
delete i;//does not require Complete type
That last line is resolved at runtime to be:
i->~Complete();
Complete::eek:perator delete(dynamic_cast<void*>( i));

So I am unclear on why you may want to do this manually in a smart
pointer?

You have a confusion here. A type is incomplete when it has only been
declared, but not defined. Consider this:

//---------------------------------------------------------------
class A; // Declaration. 'A' is an incomplete type.

A* getA(); // A function implemented somewhere else

int main()
{
A* ptr = getA();

delete ptr;
// 'ptr' gets destroyed here, but 'A' is incomplete.
// The compiler doesn't have a definition of A, and thus
// can't call its destructor.
}
//---------------------------------------------------------------

That example might seem artificial, but in fact it happens more often
than you would think. It's very typical in classes, like this:

//---------------------------------------------------------------
class SomeClass
{
class A; // Declaration. Definition is in the .cc file.
SmartPtr<A> ptr; // Dynamically allocated in the constructor

public:
SomeClass();
// Initializes 'ptr' with a dynamically allocated instance of A
};
//---------------------------------------------------------------

If SomeClass doesn't have a destructor implemented in a context where
'A' is complete, the smart pointer will have to delete the object given
to it based on the definition only.
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux:
Alf said:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:

* James Kanze:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* Juha Nieminen:
Then it occurred to me: Is there any reason this pointer
cannot be
static? Like this:
//------------------------------------------------------------------
template<typename Data_t>
class SmartPointer
{
...
static void(*deleterFunc)(Data_t*);
...
public:
SmartPointer(Data_t* d): data(d)
{
deleterFunc = &deleter;
}
...
};
template<typename Data_t>
void(*SmartPointer<Data_t>::deleterFunc)(Data_t*) = 0;
//------------------------------------------------------------------
This way the pointer to the deleter will be stored in the
program only
once, and most importantly it will not increment the size of
the smart pointer.
This feels so glaringly obvious to me now that I really
wonder why
this wasn't suggested to me to begin with. Is there some error
here I'm missing?
Yeah. Successive smart pointer instantiations can change the
common deleter func pointer.
[snip]
How?
Sorry, I reacted to the static pointer. As I wrote use a template
parameter instead. The only valid reason for having a pointer to
that function is to change the pointer, and that's apparently done
in the constructor body,
Really? Then a smart pointer working with incomplete types
(in the sense above) either does not qualify as a valid
reason or there must be a way without that (static) pointer.
Which is it? and if it is the second, which way of making a
smart pointer work with incomplete types do you have in
mind?
Consider the following:
template< typename T >
void destroy( T* );
template< typename T >
class SmartPointer
{
...
public:
~SmartPointer() { if( ... ) { destroy( myReferent ); } }
};
It achieves the same as the original code without any static
pointer.
No. With the original code, you can delete the pointer in a
context where the type is incomplete. with your code, you
can't.
Well, when you say something like that then you start me thinking if I
may have overlooked something, e.g something really basic. You force
me to actually code up a test case and check it. With at least two
different compilers.
Could you post the code? I have a little trouble deciding when and
where to put the definition for the template

template< typename T >
void destroy( T* );
Sigh.

Following is the code for James' comments.

Although this uses template parameter (which is what I recommended for
ease of use) it is trivial to remove that feature, requiring a litte
more client code.


<code file="sp.h">
#ifndef SP_H
#define SP_H

template< typename T >
void destroy( T* );

template< typename T, void (*doDestroy)(T*) = &destroy<T> >
class SmartPtr
{
private:
T* myReferent;

SmartPtr( SmartPtr const& );
SmartPtr& operator=( SmartPtr const& );

public:
SmartPtr( T* p ): myReferent( p ) {}
~SmartPtr() { doDestroy( myReferent ); }
};

#endif
</code>


<code file="x.cpp">
#include "sp.h"

class X;
X* newX();
void deleteX( X* );

int main()
{
SmartPtr<X, deleteX> p( newX() );
}
</code>


<code file="y.cpp">
#include <iostream>
using namespace std;

class X
{
private:
X( X const& );
X& operator=( X& );
public:
X() { cout << "X::<init>" << endl; }
~X() { cout << "X::<destroy>" << endl; }
};

X* newX() { return new X; }
void deleteX( X* p ) { delete p; }
</code>

It appears that you introduce a trade-off. You eliminate the pointer at
the cost of increasing the conceptual requirements for X, in particular,
a function deleteX (or some specialization of destroy(X*)) has to exist.

Or some overload of a function called by generic implementation, or e.g.
TR1's facility for unique_ptr, or whatever.

It's not a trade-off.

You can't destroy something without having available a destruction
operation, so you need that anyhow (only exception is if you're content
with assuming trivial destructor (and anyway I'm not even sure if that's
allowed, never needed it)).

There is a difference between having a destruction operation and having a
name for it. There is an obvious destruction, namely

T::~T();

which does not work for incomplete types since it assumes (as you point out)
the trivial destructor.

Some comments made in this thread were absurd. Juha's implementation,
adding a static pointer to the destruction operation, was simply
nonsensical, completely needless complexity. I'm not sure whether absurd =
nonsensical, but anyhow, this is almost as silly a debate as one might
encounter in Microsoft groups, except that here I know the folks I'm
talking to have, on earlier occasions, repeatedly exhibited signs of
intelligence, so I'm completely bewildered.

Maybe I can ease you bewilderment a little. The way I understood Juha's
challenge is to write a drop in replacement for the following template
(which is viewed as a little too bulky):

#include <tr1/memory>

template < typename T >
class smart_ptr {

std::tr1::shared_ptr<T> t_ptr;

public:

smart_ptr ( T* ptr = 0 )
: t_ptr ( ptr )
{}

smart_ptr ( smart_ptr const & other )
: t_ptr ( other.t_ptr )
{}

T & operator* ( void ) const {
return ( *t_ptr );
}

T * operator-> ( void ) const {
return ( t_ptr.operator->() );
}

};

Note that smart_ptr<X> will work as expected as long as X is complete at the
point in the program where smart_ptr<X> is constructed (TR1 guarantees
that).

"Drop in replacement" is supposed to mean that the alternative
implementation should behave like the provided implementation in all cases
(I would like to say in all test cases, but we were not given any).


I think the implementation you suggest falls short because it
requires "deleteX" do be defined. (Whether you call that a conceptual
requirement, whether it creates overhead or not, all that is immaterial:
important is just that it allows one to write a test case where your
implementation behaves observably different.)



Best

Kai-Uwe Bux
 
J

Juha Nieminen

Alf said:
* Juha Nieminen:

No.

Yes you do. Your deleter template parameter is not optional.
No. Well, yes, for this example code. But that's how example code goes,
it sort of needs to be concrete if it is to be any good to those who
don't understand.

Then show us the code which:

a) Doesn't require the user to explicitly define a deleter function for
each type.
b) Is able to properly delete the object based on an incomplete type.
(The smart pointer can require for the type to be complete at construction.)
If the smart pointer can do that then AFAICS you're content to assume a
trivial destructor for the incomplete type, in which case you don't need
any special smart pointer type -- any will do.

I showed how to do it in my original post, and you don't need to
restrict yourself to trivial destructors.
 
J

Juha Nieminen

Alf said:
Some comments made in this thread were absurd. Juha's implementation,
adding a static pointer to the destruction operation, was simply
nonsensical, completely needless complexity.

You haven't given us any working alternative. The idea is, still:

a) that the smart pointer will be able to properly destroy the object
even though the type of the object is incomplete in the context where
the smart pointer is destroyed. (It can, and in practice must, require
for the type to be complete in the context where the smart pointer is
constructed.)

b) that the smart pointer doesn't require the user to create a deleter
function for each type he wants to use the smart pointer with.

If you call my implementation with a pointer to a deleter function
absurd, then you are also calling boost::shared_ptr absurd, because
that's exactly what it does. (The only difference is that I am
suggesting that the pointer can be static, and I'm asking here why it
couldn't be. Some people seem to imply that it "doesn't work", yet I
can't see any reason why it wouldn't.)

I will concede that my idea is absolutely absurd when you show me a
better working implementation.

(In fact, from your other posts it seems that you actually believe
that it's not possible to properly delete an object in a context where
the type is incomplete. That's just not true, and the easiest
counter-example is boost::shared_ptr.)
I'm not sure whether absurd
= nonsensical, but anyhow, this is almost as silly a debate as one might
encounter in Microsoft groups, except that here I know the folks I'm
talking to have, on earlier occasions, repeatedly exhibited signs of
intelligence, so I'm completely bewildered.

Show me your working alternative, and I will concede your points.
Until that, keep your insinuations about people's intelligence to yourself.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top