| Register | FAQ | Calendar | Search | Today's Posts | Mark Forums Read |
|
#1
| |||
| |||
| 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.) |
|
#2
| |||
| |||
| * 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? |
|
#3
| |||
| |||
| 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 |
|
#4
| |||
| |||
| * 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? |
|
#5
| |||
| |||
| 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) |
|
#6
| |||
| |||
| 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 |
|
#7
| |||
| |||
| > * 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 whatsreally 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 |
|
#8
| |||
| |||
| * 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? |
|
#9
| |||
| |||
| * 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? |
|
#10
| |||
| |||
| 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.) |
![]() |
| Thread Tools | |
| Display Modes | |
In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.