Making a smart pointer which works with incomplete types

This is a discussion on Making a smart pointer which works with incomplete types within the c++ forums in Programming Languages category; 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 ...

Go Back   Application Development Forum > Programming Languages > c++

Object Mix

Register FAQ Calendar Search Today's Posts Mark Forums Read
  #1  
Old 09-07-2008, 08:00 AM
Juha Nieminen
Guest
 
Default Making a smart pointer which works with incomplete types

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.)
Reply With Quote
  #2  
Old 09-07-2008, 09:02 AM
Alf P. Steinbach
Guest
 
Default Re: Making a smart pointer which works with incomplete types

* 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. Instead make the deleter function a template parameter.

But even better, forget about this premature optimization.

For the few cases where this functionality is needed, e.g. PIMPL, just use a
boost::shared_ptr.


Cheers & hth.,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
Reply With Quote
  #3  
Old 09-07-2008, 09:20 AM
Kai-Uwe Bux
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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
Reply With Quote
  #4  
Old 09-07-2008, 09:35 AM
Alf P. Steinbach
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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


Cheers,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
Reply With Quote
  #5  
Old 09-07-2008, 09:46 AM
Pete Becker
Guest
 
Default Re: Making a smart pointer which works with incomplete types

On 2008-09-07 08:00:48 -0400, Juha Nieminen <nospam@thanks.invalid> said:

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

Reply With Quote
  #6  
Old 09-07-2008, 09:49 AM
Kai-Uwe Bux
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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

Reply With Quote
  #7  
Old 09-07-2008, 09:51 AM
Lance Diduck
Guest
 
Default Re: Making a smart pointer which works with incomplete types

> * 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{
T* data;
unsigned refcount;
void decrementReferenceCount()
{
if(data)
{
// decrement reference count and then:
if(<refcount == 0>)
{
this->D:perator()(data);//exapanded to see whats
really happening
}
}
}
};
In the vast majority of cases, D is something like
template<class T>
struct DeleteMe{
void operator()(T* d){delete d;}
};//DeleteMe has no data, therefore ECBO add no storage
Using ECBO, you will find this
sizeof(shared_ptr_imp<T,DeleteMe>)==(sizeof(T*)+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<U> alive as long as shared_ptr<T>
exists
void operator()(T* d){}
Alias(shared_ptr<U> const&k):m_keep(k){}
private:
shared_ptr<U> m_keep;
};//shared_ptr alias that preserves the ordering invaraints

Lance
Reply With Quote
  #8  
Old 09-07-2008, 09:54 AM
Alf P. Steinbach
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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


How about reading what I wrote.


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


It is.


Cheers & hth.,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
Reply With Quote
  #9  
Old 09-07-2008, 10:11 AM
Alf P. Steinbach
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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

For more flexibility (in the direction that a static pointer could add
flexibility) make the deleter a template parameter.


Cheers & hth.,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
Reply With Quote
  #10  
Old 09-07-2008, 11:27 AM
Juha Nieminen
Guest
 
Default Re: Making a smart pointer which works with incomplete types

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.)
Reply With Quote
Reply


Thread Tools
Display Modes


All times are GMT -5. The time now is 03:27 AM.


Powered by vBulletin® Version 3.7.2
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
vB Ad Management by =RedTyger=

In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.