By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,389 Members | 1,989 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,389 IT Pros & Developers. It's quick & easy.

Making a smart pointer which works with incomplete types

P: n/a
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.)
Sep 7 '08 #1
Share this Question
Share on Google+
50 Replies


P: n/a
Alf P. Steinbach wrote:
* 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?

Yeah. Successive smart pointer instantiations can change the common
deleter func pointer.
[snip]

How?

Best

Kai-Uwe Bux
Sep 7 '08 #2

P: n/a
On 2008-09-07 08:00:48 -0400, Juha Nieminen <no****@thanks.invalidsaid:
>
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:
TR1's shared_ptr (based on Boost) puts the deleter object in the
control block, along with the reference count and the pointer to the
managed resource. The deleter is not part of the type (i.e. it's not a
template argument).

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Sep 7 '08 #3

P: n/a
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?
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

Sep 7 '08 #4

P: n/a
* 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::operator()(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*)+si zeof(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<Ualive as long as shared_ptr<T>
exists
void operator()(T* d){}
Alias(shared_ptr<Uconst&k):m_keep(k){}
private:
shared_ptr<Um_keep;
};//shared_ptr alias that preserves the ordering invaraints

Lance
Sep 7 '08 #5

P: n/a
Alf P. Steinbach wrote:
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.)
Sep 7 '08 #6

P: n/a
Alf P. Steinbach wrote:
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?
Sep 7 '08 #7

P: n/a
Pete Becker wrote:
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.
Sep 7 '08 #8

P: n/a
Lance Diduck wrote:
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::operator()(data);//exapanded to see whats
really happening
If D is a template class, that line instantiates the D::operator()
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::operator() 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).
Sep 7 '08 #9

P: n/a
On Sep 7, 4:11 pm, "Alf P. Steinbach" <al...@start.nowrote:
* 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 hereI'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.

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Sep 7 '08 #10

P: n/a
On Sep 7, 11:45*am, Juha Nieminen <nos...@thanks.invalidwrote:
Lance Diduck wrote:
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::operator()(data);//exapanded to see whats
really happening

* If D is a template class, that line instantiates the D::operator()
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::operator() 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::operator delete(dynamic_cast<void*>( i));

So I am unclear on why you may want to do this manually in a smart
pointer?
Sep 7 '08 #11

P: n/a
On Sep 7, 5:35 pm, Juha Nieminen <nos...@thanks.invalidwrote:
Pete Becker wrote:
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?
No. The whole purpose of doing this is to allow different
instances to have different deleters.
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.
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.
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.
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.

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Sep 7 '08 #12

P: n/a
Alf P. Steinbach wrote:
* James Kanze:
>On Sep 7, 4:11 pm, "Alf P. Steinbach" <al...@start.nowrote:
>>* 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)(Da ta_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
Sep 7 '08 #13

P: n/a
Alf P. Steinbach wrote:
<code file="x.cpp">
#include "sp.h"

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

int main()
{
SmartPtr<X, deleteXp( 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.
Sep 7 '08 #14

P: n/a
Alf P. Steinbach wrote:
>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)?

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?
> 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,

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.
>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).

Cache.
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<Twith 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.
Sep 7 '08 #15

P: n/a
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
>Alf P. Steinbach wrote:
>>* James Kanze:
On Sep 7, 4:11 pm, "Alf P. Steinbach" <al...@start.nowrote:
* 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, deleteXp( 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
Sep 7 '08 #16

P: n/a
James Kanze wrote:
>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.
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?
>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.
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.
>>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.

Because it works?
According to my own tests, it works. I see no problems with it.
>I can't think of any reason why a static class variable
wouldn't do.

Because it doesn't work?
Exactly how does it not work?
Sep 7 '08 #17

P: n/a
Lance Diduck wrote:
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::operator 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<Aptr; // 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.
Sep 7 '08 #18

P: n/a
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
>Alf P. Steinbach wrote:
>>* Kai-Uwe Bux:
Alf P. Steinbach wrote:

* James Kanze:
>On Sep 7, 4:11 pm, "Alf P. Steinbach" <al...@start.nowrote:
>>* 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, deleteXp( 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.

>
>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.

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<Tt_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<Xwill work as expected as long as X is complete at the
point in the program where smart_ptr<Xis 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
Sep 7 '08 #19

P: n/a
Alf P. Steinbach wrote:
* Juha Nieminen:
>Alf P. Steinbach wrote:
>><code file="x.cpp">
#include "sp.h"

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

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

Here you require a "deleteX" function to be implemented by the user
alongside the X class,

No.
Yes you do. Your deleter template parameter is not optional.
>and you require this "deleteX" function to be
given as template parameter to the smart pointer.

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.)
>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.

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.
Sep 7 '08 #20

P: n/a
Alf P. Steinbach wrote:
>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.

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.
Sep 7 '08 #21

P: n/a
Alf P. Steinbach wrote:
>>> (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<Twith different destruction
behavior.

The problem is that shared_ptr always uses the exact same deleter
function for all the instances of shared_ptr<T>.

No, it doesn't; that's up to you.
Ok, boost::shared_ptr supports the user giving it a custom deleter
(rather than the automatically created one), which is why it needs a
member pointer.

My original question still stands, though.
Sep 7 '08 #22

P: n/a
Alf P. Steinbach wrote:
If you're able to assign to a static pointer to destroy function, then
you're able to have that function as template parameter.

The static pointer buys you nothing whatsoever.

It's just nonsensical complexity.
Then please go ahead and show us how. And remember: You can't require
the user to provide a deleter function explicitly, and the deleter
function must be callable from the destructor of the smart pointer even
if the object type is incomplete.

The code you posted earlier required the user to explicitly give the
smart pointer a deleter. This is something which I want to avoid
(because it can be avoided).

Requiring for the user to write a deleter for each type is nonsensical
complexity.
>"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.

It doesn't.
The deleter template parameter was not optional, and thus it requires
it. Show us version of the code which doesn't.
Sep 7 '08 #23

P: n/a
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
>>
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<Tt_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<Xwill work as expected as long as X is complete at
the point in the program where smart_ptr<Xis constructed (TR1
guarantees that).

If you're able to assign to a static pointer to destroy function, then
you're able to have that function as template parameter.
That's a different challenge.

The static pointer buys you nothing whatsoever.

It's just nonsensical complexity.
So far, I have yet to see a working proposal that beats tr1::shared_ptr
resource-wise and avoids a static pointer.

>"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.

It doesn't.
Does that mean:

a) It doesn't fall short.
b) It doesn't require deleteX to be defined.

The code you actually posted neither satisfies (a) nor (b). The code that
you asked us to read instead (the same man that admitted to not reading
code:-) may or may not qualify.

>(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.)

Given any concrete example of a general principle you can nearly always
find another application of the same general principle where the concrete
example is less than ideal. In this case you've found a more constrained,
a more specialized case (where the type is complete at instantiation
point) which can be solved more *easily*. And complain that the offered
example was too general!

I think we've played that game before.

It's silly to the extreme.
If you want to refuse the challenge, just say so. I have no intention on
making you take it.
Anyway, I just gave you my interpretation of Juha. Whether that
interpretation is correct, we can leave up to him. (It appears, though,
that he is not happy with the code you provided.)
Best

Kai-Uwe Bux
Sep 7 '08 #24

P: n/a
Kai-Uwe Bux wrote:
Anyway, I just gave you my interpretation of Juha. Whether that
interpretation is correct, we can leave up to him. (It appears, though,
that he is not happy with the code you provided.)
My original question was not really a "how do I make a smart pointer
which works with incomplete types?", but "does this work?"

My question was prompted from the realization that I could use the
same technique as boost::shared_ptr uses, but using a static function
pointer rather than a non-static one, and I was wondering why this
doesn't seem to be a common technique (in other words, maybe I have
missed some error in the code?)

Granted, by using a static function pointer it will not be possible to
define a deleter on a per-smartpointer basis, but that's not what I'm
worried about. I'm more worried about the space that pointer requires.
Making it static removes it from the smart pointer object, making the
smart pointer smaller and lighter. It seems to me that it's perfectly
possible to create a smart pointer class which works with incomplete
types without any overhead (except for that one static pointer per used
type, which is completely negligible).

I'm completely open to alternative techniques, but so far I haven't
seen anything that would meet both requirements, ie. working with
incomplete types and not burdening the user unnecessarily with
additional requirements.
Sep 7 '08 #25

P: n/a
Juha Nieminen wrote:
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.
The only thing I see is a problem with multi-threading. To be safe, I would
lock access to the deleter variable.
This feels so glaringly obvious to me now that I really wonder why
this wasn't suggested to me to begin with.
Because it is glaringly obvious :-)

Is there some error here I'm missing?
[snip]

If so, I don't see it. I remember going down the same path at some point.
The code seemed to work fine. However, there is little need to beat
tr1::shared_ptr resource-wise.
Best

Kai-Uwe Bux
Sep 7 '08 #26

P: n/a
Kai-Uwe Bux wrote:
The only thing I see is a problem with multi-threading. To be safe, I would
lock access to the deleter variable.
Of course, but that's a different issue. In this case I was wondering
about the space saving gained by this little technique.
>Is there some error here I'm missing?
[snip]

If so, I don't see it. I remember going down the same path at some point.
The code seemed to work fine. However, there is little need to beat
tr1::shared_ptr resource-wise.
In most cases I suppose it doesn't matter how much memory the smart
pointer requires. However, there may be special cases where every byte
saved can make a big difference.
Sep 7 '08 #27

P: n/a
Juha Nieminen wrote:
Kai-Uwe Bux wrote:
>The only thing I see is a problem with multi-threading. To be safe, I
would lock access to the deleter variable.

Of course, but that's a different issue. In this case I was wondering
about the space saving gained by this little technique.
[snip]

That would depend on two things: (a) the actual smart pointer implementation
and (b) the implementation of allocation. E.g., if allocation actually gets
you always multiples of 16 bytes (or 32) accounting for an overhead pointer
for memory management, you might end up not saving anything since a saving
of 4 bytes might not drop you below the next allocation threshold. Measure
and keep in mind that what works on one architecture may not be good on the
next.
Best

Kai-Uwe Bux
Sep 7 '08 #28

P: n/a
On 2008-09-07 11:35:51 -0400, Juha Nieminen <no****@thanks.invalidsaid:
Pete Becker wrote:
>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.
That's how I did it when I implemented it.
>
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).
shared_ptr<Baseptr(new Derived);

ptr holds a Base*. Its control block holds a Derived*. Both point to
the same object. shared_ptr has to keep track of the actual type passed
so that it can delete the object correctly, among other things.
>
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.
Different shared_ptr<Tobjects can manage resources of different types
derived from T, with different requirements for deletion.

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Sep 7 '08 #29

P: n/a
Alf, if I understand your approach, this summarizes its essence:

// incomplete.cpp

template<typename T>
void delete_t( T* p );

class Foo; // incomplete
void incomplete( Foo* p )
{
delete_t( p );
}

// complete.cpp

template<typename T>
void delete_t( T* p ) { delete p; }

class Foo { /* ... */ };

void incomplete( Foo* );
void complete()
{
delete_t<Foo>( 0 ); // instantiate where Foo is complete
incomplete( new Foo );
}

The delete_t function and calls to it would be in the library header. The
user just #includes a special library header that defines delete_t, so
that when uses some function in the library where Foo is complete, the
library causes delete_t to be instantiated there. Then delete_t can be
called from other functions in the library when Foo is incomplete.

The problem is if you have two users and types, where the incomplete type
is deleted in the same translation unit as the complete type is created
and delete_t is instantiated for it. In this case, it will get
instantiated for the incomplete type as well, causing an error.

Juha's original goal was to make a smart pointer class that 1) can be used
with an incomplete type, only requiring that the type be complete when the
smart pointer is constructed, and 2) doesn't require any per-instance
storage beyond a single pointer to the object itself. I absolutely cannot
understand why you have responded to Juha in the manner you have; I have
to assume he has done something to you in the past and this is some kind
of revenge.
Sep 8 '08 #30

P: n/a
Juha Nieminen schrieb:
Kai-Uwe Bux wrote:
>Anyway, I just gave you my interpretation of Juha. Whether that
interpretation is correct, we can leave up to him. (It appears, though,
that he is not happy with the code you provided.)

My original question was not really a "how do I make a smart pointer
which works with incomplete types?", but "does this work?"

My question was prompted from the realization that I could use the
same technique as boost::shared_ptr uses, but using a static function
pointer rather than a non-static one, and I was wondering why this
doesn't seem to be a common technique (in other words, maybe I have
missed some error in the code?)

Granted, by using a static function pointer it will not be possible to
define a deleter on a per-smartpointer basis, but that's not what I'm
worried about. I'm more worried about the space that pointer requires.
Making it static removes it from the smart pointer object, making the
smart pointer smaller and lighter. It seems to me that it's perfectly
possible to create a smart pointer class which works with incomplete
types without any overhead (except for that one static pointer per used
type, which is completely negligible).
shared_ptr doesn't store the deleter in the smart pointer object, but in
the struct where the reference counter is, together with the original
pointer passed in when the smart_ptr is constructed. Since you want to
store the class and the ref count together in one memory block, you
could do this:

template<typename T>
struct Holder
{
T object;
size_t refcount;
destroy_ptr<Tdestroy;
};

This would only double the overhead the ref counter had.

--
Thomas
Sep 8 '08 #31

P: n/a
Alf P. Steinbach wrote:
>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.)

I think I didn't grok what you meant here earlier.

For if you can arrange for complete type at construction, then it's
difficult for me to see how you can not just as easily arrange for
complete type at destruction, e.g., for PIMPL idiom, just give that
outer type a user defined destructor -- just an empty one.
This assumes that:

a) The smart pointer is used as a (non-static) member variable of some
class, and

b) The programmer will write a destructor for the class and implement
that destructor (even if it's just an empty implementation) in a context
where the type used by the smart pointer is complete.

If either a) or b) fails, the object might not be properly destroyed
(if the smart pointer does not support destroying object of incomplete
type).

The b) requirement is an additional requirement which is not needed.
It requires the user to first define the destructor of the outer class
in the header file and then implement it in the source code file (in a
context where the type used by the smart pointer is complete). Even in
the most minimum case it requires the user two write two additional
lines in two different files.

(Another minor point is that doing it like this makes it impossible
for the compiler to inline the destructor of the class, which might have
some minor efficiency impact in certain cases.)

Not a big deal? Well, the thing is that it's completely unnecessary to
make this requirement. It's perfectly possible to make the smart pointer
in such way that the user is not required to do this.

At the same time it will make the smart pointer usable (with
incomplete types) in other situations than as a member variable of a class.
To shave off one line of user code per class, for a relative rare case,
you think that approach with static pointer is a good idea?
It lessens the requirements of the smart pointer, making it easier to
use and more versatile. The technique has no drawbacks that I can think
of. So what would be the rational reason to not do it like that?
It might of course be complexity of that single line you're reacting to,
that it would be more difficult to understand than subtlety of smart
pointer class.

Then how about this line, always the same, no complexity, just copy n'
paste:
I'm sorry, but it just seems that you are struggling to avoid
admitting that you were wrong. (Ok, you indirectly admitted it by saying
that you "didn't understand" my original requirements (yeah, sure), but
you still are refusing to admit the soundness of the function pointer
technique, even though it's really not my invention, and used eg. by
boost::shared_ptr.)

Your proposed solution adds an unnecessary level of complexity to the
usage of the smart pointer and, what is worse, is error-prone: It
requires the user to select between different options depending on the
situation. All of this is completely unnecessary complexity because a
much simpler solution exists.
Sep 8 '08 #32

P: n/a
Alf P. Steinbach wrote:
You mean, by explaining things carefully, and even posting working code?
The first code you posted didn't work because it required the type to
be complete at destruction.

The second code might work but is unnecessarily complicated to use.
There's no need for such complication.

What you have failed to do is to explain exactly *why* the code in my
original post is "absurd". It's automatic, it's easy to use, it works
with incomplete types and it doesn't increment the size of the smart
pointer class. Exactly what is "absurd" there?

You are simply struggling around the problem of admitting, that
perhaps it's not as "absurd" after all.
Sep 8 '08 #33

P: n/a
Thomas J. Gritzan wrote:
shared_ptr doesn't store the deleter in the smart pointer object, but in
the struct where the reference counter is,
I know, but that doesn't make too much of a difference: It still
increments the memory requirements of shared_ptr by a pointer for each
managed object.

My idea was that by making the pointer static, then it increments the
memory requirements by a pointer for each *type* used, rather than for
each *object*.
together with the original
pointer passed in when the smart_ptr is constructed.
Actually no. The boost::shared_ptr class has two members: The pointer
to the managed object, and a pointer to the reference counter data
block. This is a direct copy-paste from the boost shared_ptr header:

T * px; // contained pointer
detail::shared_count pn; // reference counter

(detail::shared_count is a class which contains one pointer as member
variable.)
Since you want to
store the class and the ref count together in one memory block
I think you are confusing this thread with the other one, where I
wrote about my idea for the intrusive smart pointer (which works with
any type). My original question in this thread is not really related.
Sep 8 '08 #34

P: n/a
Alf P. Steinbach wrote:
> 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.

No no no, both I and James explained this earlier: that's not what it does.

shared_ptr allows *different* deleters for different referents of same
type.

It's a very different design goal, and very different semantics.
No, it isn't. boost::shared_ptr is guaranteed to work with incomplete
types (as long as the type is complete at construction), and it does
*not* require the user to provide it with a deleter function. (The user
*can* provide one, but is not required to do so.)

That's *exactly* what those two requirements I listed above are. It's
exactly what I want.
(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.

You might look else-thread.

I don't know whether it's "better" in a general sense, but in the sense
of getting rid of your static pointer it certainly is.
You got rid of the static pointer by adding needless complexity to the
usage of the smart pointer. You have failed to explain why that would be
a better idea.

What do you have against the static function pointer? It doesn't
consume memory basically at all (one pointer per used type, that's all)
and it's not slower than your suggestion (because in both cases the
destructor calls a function indirectly through a function pointer). The
bonus is that it can be completely automatized, unlike your suggestion.
Anyways, it demonstrates you don't need that darned static pointer (or
any pointer) even for the case where you have incomplete type at
destruction.
Of course you don't need it if you are ok with making the usage of the
smart pointer more complicated. But why do that? There's no rational reason.
> (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.)

OK, you made me laugh. Heh. :-) Thx.
Why do you have such a condescending attitude?
Sep 8 '08 #35

P: n/a
Pete Becker wrote:
shared_ptr<Baseptr(new Derived);

ptr holds a Base*. Its control block holds a Derived*. Both point to the
same object. shared_ptr has to keep track of the actual type passed so
that it can delete the object correctly, among other things.
You mean shared_ptr will be able to properly destroy an object in that
case even if the destructor of Base is not virtual?
> 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.

Different shared_ptr<Tobjects can manage resources of different types
derived from T, with different requirements for deletion.
I understand that now. Basically shared_ptr can be given different
deleter functions for different objects, even if the type is the same.

While that might be useful in some rare cases, I still feel it breaks
the basic design principle of the C++ language: You don't have to pay
for what you don't use.

In this case you have to always pay the price of a per-object deleter
even if you don't use one.
Sep 8 '08 #36

P: n/a
Kai-Uwe Bux wrote:
That would depend on two things: (a) the actual smart pointer implementation
and (b) the implementation of allocation. E.g., if allocation actually gets
you always multiples of 16 bytes (or 32) accounting for an overhead pointer
for memory management, you might end up not saving anything since a saving
of 4 bytes might not drop you below the next allocation threshold. Measure
and keep in mind that what works on one architecture may not be good on the
next.
That's true, of course. However, I have always looked for more
efficient ways of doing things (without compromising usability and safety).

If you make, for example, a big vector of smart pointers (with maybe
millions of them), the optimal situation is if your smart pointer has
the size of one single pointer (or if that's just not possible, to be as
small as possible). In this case every saved byte in the smart pointer
counts.

Also how the reference counting is implemented can have a big impact.
For example, if you use actual integers (allocated separetely) to count
the references, using a reference counter pool (which allocates only
sizeof(size_t) bytes of memory per counter), that can have an enormous
beneficial impact in memory usage if the amount of smart pointer
instances is very large.

In this situation you can make the smart pointer support incomplete
types for free: This support doesn't require any additional memory per
smart pointer instance. This can be an enormous advantage in some
situations.
Sep 8 '08 #37

P: n/a
Juha Nieminen wrote:
Thomas J. Gritzan wrote:
>shared_ptr doesn't store the deleter in the smart pointer object, but in
the struct where the reference counter is,

I know, but that doesn't make too much of a difference: It still
increments the memory requirements of shared_ptr by a pointer for each
managed object.

My idea was that by making the pointer static, then it increments the
memory requirements by a pointer for each *type* used, rather than for
each *object*.
That works if the type of the managed object _always_ is the same as
template parameter of the smart pointer object. Consider this:

shared_ptr<Derivedpd = shared_ptr<Derived>(new Derived);
shared_ptr<Basepb = pd;
shared_ptr<voidpvoid = pb;

Any of these have to be able to delete the object using the correct
deleter. So each smart pointer object holds a pointer to
a) the object it is referring to, using the correct type (i.e. void* for
the last)
b) a pointer to the control object

The control object holds
a) the reference count
b) a counter counting all strong _and_ weak counts
c) a pointer to the deleter function
d) the _original pointer_ used in constructing the first smart pointer
e) a mutex, additional stuff, etc.

The original pointer is passed to the deleter function. If the smart
pointer pvoid above has to delete the derived object, the smart pointer
object only has a void*. With a void*, you cannot destroy a Derived. So
shared_ptr has to remember the original pointer used to construct it.

Following this link, some programming techniques are described which
partly rely on having a separate deleter for every (non-shared)
shared_ptr instance:
http://www.boost.org/doc/libs/1_36_0...echniques.html
>together with the original
pointer passed in when the smart_ptr is constructed.

Actually no. The boost::shared_ptr class has two members: The pointer
to the managed object, and a pointer to the reference counter data
block. This is a direct copy-paste from the boost shared_ptr header:

T * px; // contained pointer
detail::shared_count pn; // reference counter

(detail::shared_count is a class which contains one pointer as member
variable.)
The pointer object (i.e. each instance of shared_ptr) only has these,
but the original pointer is stored in the control object. See above.

[about Juha Nieminen's smart pointer]
>Since you want to
store the class and the ref count together in one memory block

I think you are confusing this thread with the other one, where I
wrote about my idea for the intrusive smart pointer (which works with
any type). My original question in this thread is not really related.
I know this is another thread, but I thought you asked for a technique
for your smart pointer.

By the way, your wording is confusing. Your SP is not intrusive since
you don't have to change the managed class. It is non-intrusive like
shared_ptr but it allocates the control object together with the managed
class.

--
Thomas
Sep 8 '08 #38

P: n/a
On Sep 8, 4:19 pm, Juha Nieminen <nos...@thanks.invalidwrote:
Pete Becker wrote:
shared_ptr<Baseptr(new Derived);
ptr holds a Base*. Its control block holds a Derived*. Both
point to the same object. shared_ptr has to keep track of
the actual type passed so that it can delete the object
correctly, among other things.
You mean shared_ptr will be able to properly destroy an object
in that case even if the destructor of Base is not virtual?
Yes, provided it was originally constructed with a pointer to
derived. E.g.:
boost::shared_ptr< Base p( new Derived ) ;
is fine, but
Base* tmp = new Derived ;
boost::shared_ptr< Base p( tmp ) ;
isn't (unless Base has a virtual destructor.
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.
Different shared_ptr<Tobjects can manage resources of
different types derived from T, with different requirements
for deletion.
I understand that now. Basically shared_ptr can be given
different deleter functions for different objects, even if the
type is the same.
Exactly.
While that might be useful in some rare cases, I still feel it
breaks the basic design principle of the C++ language: You
don't have to pay for what you don't use.
I find it relatively rare to use a boost::shared_ptr without a
custom deleter. And the cost is very close to zero, once the
design requirement is established that it must work with
"unco-operative" types (i.e. classes which don't provide the
counter as a member somehow).
In this case you have to always pay the price of a per-object
deleter even if you don't use one.
And what is that price?

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Sep 8 '08 #39

P: n/a
Alf P. Steinbach wrote:
* Juha Nieminen:
>>
I'm sorry, but it just seems that you are struggling to avoid
admitting that you were wrong. (Ok, you indirectly admitted it by saying
that you "didn't understand" my original requirements (yeah, sure), but
you still are refusing to admit the soundness of the function pointer
technique, even though it's really not my invention, and used eg. by
boost::shared_ptr.)

No, shared_ptr does not use a static pointer.
It uses a regular pointer, which is even worse.

Your whole point was that storing this pointer anywhere could be
avoided altogether.
> Your proposed solution adds an unnecessary level of complexity to the
usage of the smart pointer and, what is worse, is error-prone: It
requires the user to select between different options depending on the
situation.

There's no error-prone-ness: you can't do wrong.
So an additional requirement of "if you instantiate the smart pointer
in an environment where the type is complete, include this file, else
include this another file" is not error-prone?

With the boost::shared_ptr solution there's only one single include
file and no possibility of error.
>All of this is completely unnecessary complexity because a
much simpler solution exists.

What complexity.
Having to choose between different header files depending on whether
the type might be incomplete or not.
The simplest is to do exactly what you want to achieve,
telling the compiler exactly what that is.
That's not the simplest thing to do. The simplest thing is when you
*don't have to* tell the compiler about your special cases and instead
the compiler can deduce them itself automatically.

If having to do everything explicitly is the "simplest" thing to do,
then you should switch to C and forget about C++. There you won't have
to worry about the compiler or the libraries doing things for you
automatically.
Instead of introducing
roundabout detours via static pointers, which *is* complexity.
We are talking about complexity from the point of view of the user, in
other words, the complexity of the public interface of the class and its
requirements, ie. how complicated it is to use.

How complicated the private implementation of a class is, is
completely inconsequential as long as it causes the public interface to
be easy to use and safe, and preferably the class as efficient as possible.

From the computer's point of view one function pointer per used type
is nothing. It doesn't even require any significant amounts of memory.
Thus the implementation is very efficient with respect to memory
consumption.

Adding requirements to the public interface of the class (eg.
requiring the user to write additional lines) when there's no functional
or significant efficiency reasons to do so simply doesn't make sense.
I have trouble grasping why you're arguing for that pointer, given that
your question was how to get rid of it.
Where did you get this impression? I never said I want to get rid of
it. What I said is that I want to take it out of the smart pointer
object so that it doesn't increase its size.

Of course getting completely rid of it (without making the usage of
the smart pointer more complicated) would be cool, but it's technically
impossible. The simple fact is: If the object must be deletable by the
smart pointer in a context where the type is incomplete, the destructor
of the smart pointer cannot delete it directly, nor can it instantiate
any deleter function which does so.

This goal can be achieved by making the user create such a function
and giving it to the smart pointer. No matter how many tricks you try to
invent to automatize this as much as possible, it will always require
the user to explicitly write something extra. This is needlessly
burdensome and error-prone. It's needlessly so because it can be easily
avoided.

Another way of achieving this is by the smart pointer automatically
instantiating the deleter function itself. The only place where it can
do so (as per the specs) is in the constructor. After this the only way
the destructor can call this automatically generated function without
actually instantiating it itself as well, is through a function pointer.
There's no way of passing this function pointer from the constructor to
the destructor other than storing it somewhere.

This is the exact technique that boost::shared_ptr uses to
automatically support incomplete types.
Sep 8 '08 #40

P: n/a
Thomas J. Gritzan wrote:
I know this is another thread, but I thought you asked for a technique
for your smart pointer.
In fact, I originally wasn't thinking about my intrusive smart pointer
when I made the original post, but this discussion got me thinking about
how I could make it support incomplete types (preferably without too
much overhead), and I have got a few good ideas. It may well be possible
to make it support incomplete types almost for free (except that the
deleter must be called indirectly when the object is destroyed, but
that's probably negligible given that object deletion itself is a
relatively heavy operation).
By the way, your wording is confusing. Your SP is not intrusive since
you don't have to change the managed class. It is non-intrusive like
shared_ptr but it allocates the control object together with the managed
class.
It may be that using "intrusive" in the name of my RefPtr class might
not be technically completely correct. However, for almost all intents
and purposes it behaves like a classical intrusive smart pointer: The
size of the smart pointer class itself is that of one single pointer,
and the reference count is in the same memory block as the managed
object rather than being allocated separately, and thus it has all the
efficiency advantages of a classical intrusive smart pointer.

I can't think of a more descriptive term for this...
Sep 8 '08 #41

P: n/a
Alf P. Steinbach wrote:
>>It's a very different design goal, and very different semantics.

No, it isn't. boost::shared_ptr is guaranteed to work with incomplete
types (as long as the type is complete at construction), and it does
*not* require the user to provide it with a deleter function. (The user
*can* provide one, but is not required to do so.)

The "No, it isn't" is incorrect.

The rest is correct, but unrelated.
Unrelated? So boost::shared_ptr just happens by lucky chance to
support incomplete types exactly the way I have been describing, even
though that's not the goal of boost::shared_ptr?
> You got rid of the static pointer by adding needless complexity to the
usage of the smart pointer. You have failed to explain why that would be
a better idea.

You have failed to explain what the alleged "complexity" consists of.
The additional requirements for the usage of the smart pointer in your
two different solutions.

boost::shared_ptr and my variant do not need *any* additional
requirements whatsoever from the user, compared to a smart pointer
implementation which does not support incomplete types. They can be used
*exactly* as you would use a simple vanilla smart pointer which works
only for complete types.

Having to write even one single additional line compared to this is
useless complexity and is not needed.
You have failed to explain why you think the static pointer is a good
idea.
Because it doesn't change the public interface nor the usage of the
smart pointer in any way, compared to a basic smart pointer. Everything
happens automatically and opaquely.

Your solutions add extra requirements for the user. For no good reason.
You started by asking how to get rid of it
Where exactly?
and that's
inconsistent with present position, which begs the question, why the
change of heart?
You might have a point if I had asked how to get rid of the function
pointer, but I haven't.

The only thing I have asked is why the function pointer could not be
static rather than non-static.
Sep 8 '08 #42

P: n/a
James Kanze wrote:
>In this case you have to always pay the price of a per-object
deleter even if you don't use one.

And what is that price?
sizeof(T*) * amount_of_objects
Sep 8 '08 #43

P: n/a
Alf P. Steinbach wrote:
In the face of complete code demonstrating that that isn't necessary,
you maintain that it's necessary.
Do you have some kind of difficulty in understanding the concept of
not changing the public interface of a class when its internal
implementation details change, and not adding additional requirements
for its use, when that is not necessary?

The fact is: A regular smart pointer which does not support incomplete
types can be changed into one which does, and this without modifying its
public interface and usage in any way whatsoever, and without adding any
requirements whatsoever for its usage.

You are heavily opposed to this, and instead would want to change the
usage of the smart pointer to be more complicated just because for
whatever reason I cannot fathom you abhor the idea of having a function
pointer stored somewhere (even though that's exactly what eg.
boost::shared_ptr does).
> I'd give thanks if your solutions worked, but they don't.

Worked fine for me.

I'm at a loss as for how you have managed to make them not work.
You simply refuse to comprehend the concept of not adding additional
requirements for the user when that's not necessary in any way.

Of course your solutions "work". They work in the same way as using
just raw pointers and making the user call a deleter function manually
when he wants to delete the object. It indeed is possible to create a
perfectly working program that way.

Where that fails is in making the user's life easier rather than harder.
>They just
make the usage of the class more complicated for no reason.

Also regarding the alleged complication.
Any additional requirement is useless complication.
Sep 8 '08 #44

P: n/a
Alf P. Steinbach wrote:
> Your whole point was that storing this pointer anywhere could be
avoided altogether.

Demonstrated now two or three times
By making the usage of the smart pointer more complicated.

Why not forget the whole smart pointer and use raw pointers instead?
Then it certainly is not necessary to store *any* additional data.

Oh, it's more complicated that way? But that's not important. What's
important is that no function pointers are stored anywhere.
> So an additional requirement of "if you instantiate the smart pointer
in an environment where the type is complete, include this file, else
include this another file" is not error-prone?

Well it isn't like that. It's not an either-or for the includes. And you
don't actually need to include anything for the instantiation.
Wow, now you are able to instantiate your smart pointer without even
declaring it? That's cool.
> Having to choose between different header files depending on whether
the type might be incomplete or not.

You have misunderstood. There is no choice of header files. There's just
a choice of where to get a definition from: write it (1 line) or
#include it (1 line), which anyway makes that part very explicit, which
is good.
It makes the usage inconsistent, adding additional choices for the
user to make, which is bad. Not only bad, but unnecessary.
> That's not the simplest thing to do. The simplest thing is when you
*don't have to* tell the compiler about your special cases and instead
the compiler can deduce them itself automatically.

If you're not aware that you're dealing with incomplete type then
something is very wrong.
Now it's you who is babbling.

Do you understand the concept of abstraction? The more abstract your
program is, the better. When you *don't have to know* how things are
implemented, the better.
> We are talking about complexity from the point of view of the user, in
other words, the complexity of the public interface of the class and its
requirements, ie. how complicated it is to use.

How complicated the private implementation of a class is, is
completely inconsequential as long as it causes the public interface to
be easy to use and safe, and preferably the class as efficient as
possible.

Yeah, except none of that is relevant.
It might not be relevant to you, but for people who want to keep their
classes as simple and easy to use as possible, it is.
> From the computer's point of view one function pointer per used type
is nothing. It doesn't even require any significant amounts of memory.
Thus the implementation is very efficient with respect to memory
consumption.

You've argued the opposite earlier in this thread.
Where exactly? Please point me to the specific post. I dare you.

My original post was *precisely* about saving memory by using a static
function pointer rather than a non-static one. The non-static member
pointer consumes memory for each object instance, while a static one
doesn't. It only consumes memory for each used type, thus potentially
saving enormous amounts of memory.

Perhaps you have reading comprehension problems?
I didn't agree then
Agreed about what? You are not even making any sense anymore.
>>I have trouble grasping why you're arguing for that pointer, given that
your question was how to get rid of it.

Where did you get this impression? I never said I want to get rid of
it. What I said is that I want to take it out of the smart pointer
object so that it doesn't increase its size.

"I'm more worried about the space that pointer requires.
Making it static removes it from the smart pointer object, making the
smart pointer smaller and lighter. It seems to me that it's perfectly
possible to create a smart pointer class which works with incomplete
types without any overhead"

And yes, it's possible, and you've been shown how.
Maybe you should read that quote again?

I said there: "If you make the function pointer static, it removes the
overhead from the smart pointer".

Do you understand what my actual question was? And no, it was not "how
do I get rid of this pointer completely?"
> This goal can be achieved by making the user create such a function
and giving it to the smart pointer. No matter how many tricks you try to
invent to automatize this as much as possible, it will always require
the user to explicitly write something extra. This is needlessly
burdensome and error-prone. It's needlessly so because it can be easily
avoided.

Another way of achieving this is by the smart pointer automatically
instantiating the deleter function itself. The only place where it can
do so (as per the specs) is in the constructor. After this the only way
the destructor can call this automatically generated function without
actually instantiating it itself as well, is through a function pointer.
There's no way of passing this function pointer from the constructor to
the destructor other than storing it somewhere.

You've been given complete code that demonstrates that conclusion to be
false.
A complete code which requires the user to write additional code to
create the deleter function. Unnecessarily.
Sep 8 '08 #45

P: n/a
On Sep 8, 6:29 pm, Juha Nieminen <nos...@thanks.invalidwrote:
James Kanze wrote:
In this case you have to always pay the price of a per-object
deleter even if you don't use one.
And what is that price?
sizeof(T*) * amount_of_objects
Maybe, maybe not. On a Sun Sparc, in 32 bit mode, the price is
0. (Allocation granularity is 8 bytes, to ensure alignment of a
double. The reference count is 4 bytes. There are thus 4 bytes
leftover when it is allocated. Just the size of a T*.)

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Sep 9 '08 #46

P: n/a
James Kanze wrote:
Maybe, maybe not. On a Sun Sparc, in 32 bit mode, the price is
0. (Allocation granularity is 8 bytes, to ensure alignment of a
double. The reference count is 4 bytes. There are thus 4 bytes
leftover when it is allocated. Just the size of a T*.)
Question: Will shared_ptr in the new standard support user-defined
custom allocators for the smart pointer to allocate its data?
Sep 9 '08 #47

P: n/a
On Sep 9, 5:13 pm, Juha Nieminen <nos...@thanks.invalidwrote:
James Kanze wrote:
Maybe, maybe not. On a Sun Sparc, in 32 bit mode, the price is
0. (Allocation granularity is 8 bytes, to ensure alignment of a
double. The reference count is 4 bytes. There are thus 4 bytes
leftover when it is allocated. Just the size of a T*.)
Question: Will shared_ptr in the new standard support
user-defined custom allocators for the smart pointer to
allocate its data?
I hope not. Too much added complexity for too little benefit.

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Sep 10 '08 #48

P: n/a
James Kanze wrote:
On Sep 9, 5:13 pm, Juha Nieminen <nos...@thanks.invalidwrote:
>James Kanze wrote:
Maybe, maybe not. On a Sun Sparc, in 32 bit mode, the price is
0. (Allocation granularity is 8 bytes, to ensure alignment of a
double. The reference count is 4 bytes. There are thus 4 bytes
leftover when it is allocated. Just the size of a T*.)
>Question: Will shared_ptr in the new standard support
user-defined custom allocators for the smart pointer to
allocate its data?

I hope not. Too much added complexity for too little benefit.
The draft n2691 has an allocator for shared_ptr. Interestingly, I did not
find a get_allocator() function.
Best

Kai-Uwe Bux
Sep 10 '08 #49

P: n/a
On 2008-09-10 03:30:50 -0400, James Kanze <ja*********@gmail.comsaid:
On Sep 9, 5:13 pm, Juha Nieminen <nos...@thanks.invalidwrote:
>James Kanze wrote:
>>Maybe, maybe not. On a Sun Sparc, in 32 bit mode, the price is
0. (Allocation granularity is 8 bytes, to ensure alignment of a
double. The reference count is 4 bytes. There are thus 4 bytes
leftover when it is allocated. Just the size of a T*.)
>Question: Will shared_ptr in the new standard support
user-defined custom allocators for the smart pointer to
allocate its data?

I hope not. Too much added complexity for too little benefit.
It's there, and the complexity in the specification isn't bad, because
the allocator isn't part of the type. There's an additional constructor
that takes an extra argument that specifies an allocator, an additional
overload of reset() that also takes an extra argument, and a new
non-member template function, allocate_shared, that takes an allocator.

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Sep 10 '08 #50

50 Replies

This discussion thread is closed

Replies have been disabled for this discussion.