class RefCountObject
{
private:
// Reference count
unsigned int value;
public:
virtual void dispose()
{ delete this; }
inline void addref() { ++ this->value; }
inline void release()
{
if( 0 == -- this->value )
this->dispose();
}
RefCountObject() : value(1) {}
virtual ~RefCountObject() {}
};
struct RefCount : virtual RefCountObject {};
// usage...
class Foo : public RefCount
{
// ...
};
Foo* p = new Foo;
p->addref();
p->release();
p->release();
🔒 Locked
"Inline" reference counting?
Advertisement
Original Post
Hi. I am currently using slightly modified implementation of reference counting which I think as an improvement. I'd like to know what others think about it, as so many times I miss something important and what I think as improvement is actually a bad idea. OK here goes my code; What I think as improvement is that addref() and release() functions will be inlined thus speeding up the code. But I haven't seen such implementation in others' code and now wonder why they don't do it if it's actually good idea? Did I miss something? Thanks in advance.
Oh dear.
I think I'll respond before people with harsher words do, but you're a bit misguided. The compiler is free to ignore your demands to inline code, and is also (and encouraged to be) free to inline code you don't specify. That means for titchy little functions like the ones in Boost.SharedPtr, it'll almost certainly be inlined (consider: in C++, any function defined inside a class definition has inline applied to it implicitly). The reason people use smart pointers over doing "addref/release" manually is because doing it manually adds nothing. You are still required to manually track "usages". Using smart pointers alleviate the overhead of tracking "usages", making you only responsible for defining "ownership" - which is really easy. Maybe you haven't seen it before, having said so yourself?
There's no reason not to use smart pointers - they are customizable enough to be used for COM objects, D3D resources, etc. You only need to tweak things a little. Plus, they're well, well, well tested - they're in HUGE PILES of production software in the world today. Considering how small the shared-pointers are, it's safe to say that all bugs in them have been ironed out. They're more flexible, more portable, and better documented.
Basically, your reinvention of the wheel offers nothing over the other (industry standard, tested, debugged, flawless) wheel.
I think I'll respond before people with harsher words do, but you're a bit misguided. The compiler is free to ignore your demands to inline code, and is also (and encouraged to be) free to inline code you don't specify. That means for titchy little functions like the ones in Boost.SharedPtr, it'll almost certainly be inlined (consider: in C++, any function defined inside a class definition has inline applied to it implicitly). The reason people use smart pointers over doing "addref/release" manually is because doing it manually adds nothing. You are still required to manually track "usages". Using smart pointers alleviate the overhead of tracking "usages", making you only responsible for defining "ownership" - which is really easy. Maybe you haven't seen it before, having said so yourself?
// in some scope somewhere{ boost::shared_ptr<int> my_int(new int(6)); // use my_int as a regular pointer ...}// end of scope, pow! memory deallocated.There's no reason not to use smart pointers - they are customizable enough to be used for COM objects, D3D resources, etc. You only need to tweak things a little. Plus, they're well, well, well tested - they're in HUGE PILES of production software in the world today. Considering how small the shared-pointers are, it's safe to say that all bugs in them have been ironed out. They're more flexible, more portable, and better documented.
Basically, your reinvention of the wheel offers nothing over the other (industry standard, tested, debugged, flawless) wheel.
I'm not sure if you know this, but just by defining it in the class makes it implicitly inline. I would say it is probably a good idea since it is such a small operation and won't bloat the code too much. The compiler will ignore it if it doesn't think that it is a good idea as well, so I would say go for it.
I would recommend using tr1::shared_ptr or boost::shared_ptr, unless you are really into having your own reference counter.
I would recommend using tr1::shared_ptr or boost::shared_ptr, unless you are really into having your own reference counter.
Quote:
Original post by muse1987
What I think as improvement is that addref() and release() functions will be inlined thus speeding up the code. But I haven't seen such implementation in others' code and now wonder why they don't do it if it's actually good idea?
It depends which compiler they're targetting. Boost being mostly templated (and hence header-centric) doesn't and lets compiler inline as it chooses.
For some platforms, smaller code is actually faster, so inlining isn't strictly desired, and is performed after profiling.
Despite convenience of reference counting, passing references all over the place frequently shouldn't be common. As such, the number of references and cost of count is usually minimal.
boost::shared_ptr is exception since it performs thread-safe reference counting, adding non-trivial overhead for simple increment. Still, it's not noticable for common usage. Frequent creations of shared_ptr or passing it in tight loops however can be a problem.
If reference counting is automatic one can still pass smart pointers by reference and avoid the count change unless really needed.
So typically the act of counting reference itself shouldn't be a problem, especially not with explicit reference counting (vs. implicit via assingment/copy-constructor).
@_goat : Surely I have implemented a smart pointer. I just ommitted it as I thought that's not important here.
@Treb : Yes I know they're implicitly inlined. I just specify it because I prefer long code [lol]
@_goat and Treb : boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Sorry I didn't mean to start a flame war. I just wanted to say what I think. You're welcome to correct me.
@Treb : Yes I know they're implicitly inlined. I just specify it because I prefer long code [lol]
@_goat and Treb : boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Sorry I didn't mean to start a flame war. I just wanted to say what I think. You're welcome to correct me.
@Antheus :
Oh I forgot it. Thanks for telling me that. I just corrected my code to be thread-safe.
Well I think that makes sense.
Quote:
boost::shared_ptr is exception since it performs thread-safe reference counting
Oh I forgot it. Thanks for telling me that. I just corrected my code to be thread-safe.
Quote:
If reference counting is automatic one can still pass smart pointers by reference and avoid the count change unless really needed.
Well I think that makes sense.
Inline is evil and rubbish.
Quote:
Original post by muse1987
@_goat and Treb : boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Sorry I didn't mean to start a flame war. I just wanted to say what I think. You're welcome to correct me.
You know, requiring a class to inherit from your base class (if it didn't already inherit from something else) also adds (at least) 8 bytes of overhead (4 for the counter and 4 for the vtable pointer)... Sure it's allocated in one chunk, rather than 2, but it's still there.
And the overhead of your method exists no matter how many references you have. You'd be surprised - most shared_ptr's don't have more than one reference. Of course, that depends on usage, so may not be true in all cases.
In any case, this seems like a case of premature optimization, to me.
On another topic, most compilers these days have a feature called "whole program optimization". Basically, the linker is able to do various optimizations in addition to the compiler. So even if the methods were not defined in the header, they could still be inlined.
Quote:If you're really worried about this, take a look at boost::intrusive_ptr. It has many of the advantages of shared_ptr (widely used, thoroughly tested), but allows you to implement your own reference counting mechanism.
boost::shared_ptr (IMHO) has disadvantage in performance. I'm not saying it's badly implemented, rather that comes from its versatility. As _goat mentioned boost::shared_ptr can even hold pointer to int, while my smart pointer can hold only classes derived from RefCount. In order to allow that boost::shared_ptr allocates an object internally, called boost::detail::sp_counted_base. So what happens? Each object stored in shared_ptr will cause another object allocated on the heap(only when two or more shared_ptr point to same object but I'm sure that's 99% case). I see the sizeof(sp_counted_base) == 8. When it allocated on the heap there's memory overhead. I think it actually consumes 32 bytes in Win32. Also caring that heap operations are relatively costly I minimize dependency to dynamic memory in my code. That's why I wrote my own.
Quote:I haven't done any profiling to see if an internal (intrusive) or external (shared_ptr style) ref-counter is more optimal... but, the game engine we use at work uses the intrusive (base class) method for reference-counted objects.
Original post by Codeka
You know, requiring a class to inherit from your base class (if it didn't already inherit from something else) also adds (at least) 8 bytes of overhead (4 for the counter and 4 for the vtable pointer)... Sure it's allocated in one chunk, rather than 2, but it's still there.
In any case, this seems like a case of premature optimization, to me.
Quote:Out of curiosity - how did you go about making your code thread-safe? --Mutual exclusion or atomic variables?
Original post by muse1987
Thanks for telling me that. I just corrected my code to be thread-safe.
Truly thread-safe (and high performance) ref-counting is often harder than you first might think...
Thanks for all your informations
@Codeka: Not necessarily 8 bytes. They can share same vftable pointer. Though they'll need seperate ones if MI involved.
@DerekSaw: I think it's exception safe but not sure. Many cases I have to work without C++ exceptions so I'm not much exprienced with that part. I'll post my code and perhaps you could find some bugs:
@jyk: I will. Thanks for letting me know.
@Hodgman: I just used InterlockedIncrement and InterlockedDecrement Win32 API functions. Ah, and _InterlockedIncrement/_InterlockedDecrement instrinsic functions if _MSC_VER >= 1300
@Codeka: Not necessarily 8 bytes. They can share same vftable pointer. Though they'll need seperate ones if MI involved.
@DerekSaw: I think it's exception safe but not sure. Many cases I have to work without C++ exceptions so I'm not much exprienced with that part. I'll post my code and perhaps you could find some bugs:
template< typename T > class SharedPointer{private: T* object; void drop() { if( this->object ) { T* temp = this->object; this->object = 0; temp->RefCountObject::release(); } } void hold( T* object ) { if( object ) object->RefCountObject::addref(); this->object = object; }public: // Default constructor and the destructor SharedPointer() : object(0) {} ~SharedPointer() { this->drop(); } // Constructor / Assignment operator which takes T* as argument explicit SharedPointer( T* object ) { this->hold( object ); } SharedPointer< T >& operator = ( T* object ) { this->drop(); this->hold( object ); return *this; } // Copy constructor and Assignment operator SharedPointer( SharedPointer< T > const& rhs ) { this->hold( rhs.object ); } SharedPointer< T >& operator = ( SharedPointer< T > const& rhs ) { if( this == &rhs ) return *this; this->drop(); this->hold( rhs.object ); return *this; } // Attaching / Detaching object without changing its ref-count SharedPointer< T >& attach( T* object ) { this->drop(); this->object = object; return *this; } T* detach() { T* temp = this->object; this->object = 0; return temp; } // operator T* () const { return this->object; } T* operator -> () const { return this->object; }};@jyk: I will. Thanks for letting me know.
@Hodgman: I just used InterlockedIncrement and InterlockedDecrement Win32 API functions. Ah, and _InterlockedIncrement/_InterlockedDecrement instrinsic functions if _MSC_VER >= 1300
All or most of those ugly "this->" bits should be able to be removed.
You should read Guru of the Week numbers 11 and 23, and if you haven't seen that site before I recommend endeavouring to read as many of them as you can!
If it is necessary to check for self-assignment to prevent things going wrong if it is used, then you've done it wrong.
You should also look up the Copy and Swap idiom
You might also want to look into how well your class would work in the context of a fully const-correct program, and whether your refcount should be marked as mutable.
I'm with the crowd that prefer NULL over 0 when it comes to readability of code dealing with pointers.
You should read Guru of the Week numbers 11 and 23, and if you haven't seen that site before I recommend endeavouring to read as many of them as you can!
If it is necessary to check for self-assignment to prevent things going wrong if it is used, then you've done it wrong.
You should also look up the Copy and Swap idiom
You might also want to look into how well your class would work in the context of a fully const-correct program, and whether your refcount should be marked as mutable.
I'm with the crowd that prefer NULL over 0 when it comes to readability of code dealing with pointers.
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement