Reputation: 30115
Currently ive got some reference counted classes using the following:
class RefCounted
{
public:
void IncRef()
{
++refCnt;
}
void DecRef()
{
if(!--refCnt)delete this;
}
protected:
RefCounted():refCnt(0){}
private:
unsigned refCnt;
//not implemented
RefCounted(RefCounted&);
RefCounted& operator = (RefCounted&};
};
I also have a smart pointer class that handles reference counting , all though its not uniformly used (eg in one or two bits of performance critical code, where I minimised the number of IncRef and DecRef calls).
template<class T>class RefCountedPtr
{
public:
RefCountedPtr(T *p)
:p(p)
{
if(p)p->IncRef();
}
~RefCountedPtr()
{
if(p)p->DecRef();
}
RefCountedPtr<T>& operator = (T *newP)
{
if(newP)newP->IncRef();
if(p) p ->DecRef();
p = newP;
return *this;
}
RefCountedPtr<T>& operator = (RefCountedPtr<T> &newP)
{
if(newP.p)newP.p->IncRef();
if(p) p ->DecRef();
p = newP.p;
return *this;
}
T& operator *()
{
return *p;
}
T* operator ->()
{
return p;
}
//comparison operators etc and some const versions of the above...
private:
T *p;
};
For the general use of the classes themselves I plan to use a reader/writer locking system, however I dont really want to have to get a writer lock for every single IncRef and DecRef call.
I also just thought of a scenario where the pointer may be invalidated just before the IncRef call, consider:
class Texture : public RefCounted
{
public:
//...various operations...
private:
Texture(const std::string &file)
{
//...load texture from file...
TexPool.insert(this);
}
virtual ~Texture()
{
TexPool.erase(this);
}
freind CreateTextureFromFile;
};
Texture *CreateTexture(const std::string &file)
{
TexPoolIterator i = TexPool.find(file);
if(i != TexPool.end())return *i;
else return new Texture(file);
}
ThreadA ThreadB t = CreateTexture("ball.png"); t->IncRef(); ...use t... t2 = CreateTexture("ball.png");//returns *t ... thread suspended... t->DecRef();//deletes t ... ... t2->IncRef();//ERROR
So I guess I need to change the ref counting model entirely, the reason I added a ref after the return in the design was to support things like the following:
MyObj->GetSomething()->GetSomethingElse()->DoSomething();
rather than having to:
SomeObject a = MyObj->GetSomething();
AnotherObject *b = a->GetSomethingElse();
b->DoSomething();
b->DecRef();
a->DecRef();
Is there a clean way for fast reference counting in c++ in a multi threaded environment?
Upvotes: 6
Views: 9573
Reputation: 3176
Take a look at this pdf: http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf
This describes a reference counting system which does not need any locking. (Well you need to "pause" threads one at a time that can count as locking.) It also collects garbage cycles. The disadvantage is that it is a lot more complex. There are also some important things left as an exercise for the reader. Like what happens when a new thread is created or an old one deleted or how to deal with inherently acyclic objects. (If you decide to do this please let me know how you sloved these.)
Upvotes: 0
Reputation:
Did you want thread-safe or atomically thread-safe? boot::shared_ptr is merely thread-safe. You still need to "own" a shared_ptr in order to copy it safely.
There's some experimental stuff I did on atomically thread-safe reference counting here at http://atomic-ptr-plus.sourceforge.net/ which can give you an idea of what's involved.
Upvotes: 2
Reputation: 1310
I think you do need critical sections for this particular design. One place where it is required is CreateTexture, because otherwise you are running into a risk of having more than one identical texture object in the system. And, in general, if several threads can create and destroy the same texture it makes it "mutable shared state".
Upvotes: 0
Reputation: 41096
Your cache needs to use a boost::weak_ptr
or a similar construct.
Upvotes: 1
Reputation: 45335
Unless you know it's a specific bottleneck I'd just use boost::shared_ptr
It is very fast however there is a bit of extra overhead in the extra control block being allocated. On the other hand it has many benefits:
boost
which if you aren't you should beAlso note, you probably won't want a reader\writer lock for a ref counted object. The contention is minimal and the extra overhead will completely overwhelm any benefits you would have. The shared pointer is implemented with a chip level atomic int operation, this is significantly better than a normal mutex which is significantly faster than a reader\writer lock.
Upvotes: 10
Reputation: 73041
If you don't want to use boost or C++0X, but you still want lockless refcounting, you can do so by including the correct platform-specific atomic-increment/atomic-decrement assembly routines in your code. As an example, here's the AtomicCounter class that I use for my reference counting; it works under most common OS's:
https://public.msli.com/lcs/muscle/html/AtomicCounter_8h_source.html
Yes, it's a nasty mess of #ifdefs. But it does work.
Upvotes: 6
Reputation: 7586
boost::shared_ptr and Poco::SharedPtr both wrap this idiom in a freestanding smart pointer.
If you want intrusive reference counting, as you've demonstrated above, Poco's AutoPtr is a good, working implementation.
EDIT: I would have added links, but I was too low on reputation. Google for any of the class names, and you should find your way.
Upvotes: 1
Reputation: 231063
Your main problem is that you don't acquire a reference before CreateTexture returns. If you're open-coding like this, the easiest way to handle it is to have a lock around TexPool which is also taken when releasing references before the delete, like so:
// PSEUDOCODE WARNING: --refcnt MUST be replaced by an atomic decrement-and-test
// Likewise, AddRef() MUST use an atomic increment.
void DecRef() {
if (!--refcnt) {
lock();
if (!refcnt)
delete this;
unlock();
}
}
and:
Texture *CreateTexture(const std::string &file)
{
lock();
TexPoolIterator i = TexPool.find(file);
if(i != TexPool.end()) {
*i->AddRef();
unlock();
return *i;
}
unlock();
return new Texture(file);
}
That said, as others have mentioned, boost::shared_ptr (aka std::tr1::shared_ptr) implements this all in a lockless, safe way, and also has support for weak pointers, which will help with your texture cache.
Upvotes: 1
Reputation: 7209
osg, OpenSceneGraph has such a structure.
you derive your classes from osg::Referenced and you dont care about destructor even in multithread.
you just create classes as :
osg::ref_ptr<MyClass> m = new MyClass();
instead of:
MyClass* m = new MyClass();
Upvotes: 2
Reputation: 6128
Make the reference counting atomic and you won't need any lock. In Windows ::InterlockedIncrement and ::InterlockedDecrement can be used. In C++ 0x, you have atomic<>.
Upvotes: 16