Hello All,
I'm running into kindof an annoying problem and would need some advice...
Let's say I have a bunch of small MyObject's, that can construct bigger MyExtendedObject's. MyExtendedObject's are big and CPU consuming so construction is lazy, and I try do remove them from memory as soon as possible:
MyExtendedObject * MyObject::GetExtentedObject(){
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
}
++ref_;
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
if(0 == (--ref_))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
}
Extended objects are only constructed once at the beginning and are destroyed when the last caller releases them. Note that some may be constructed more than once but this is not an issue here.
Now, this is absolutely not thread-safe so I did a "naive" thread-safe implementation:
MyExtendedObject * MyObject::GetExtentedObject(){
Lock();
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
}
++ref_;
Unlock();
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
Lock();
if(0 == (--ref_))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
Unlock();
}
This is better but now I spend some non neglectable amount of time locking and unlocking...
I had the feeling we could pay the lock/unlock only when constructing or destructing.
I came up with this solution:
MyExtendedObject * MyObject::GetExtentedObject(){
long addref = InterlockedCompareExchange(&ref_, 0, 0);
long result;
do{
result = addref + 2;
} while ((result-2) != (addref = InterlockedCompareExchange(&ref_, result, addref)));
if(0 == (result&1)){
Lock();
if(NULL == ext_obj_){
ext_obj_ = new MyExtendedObject;
InterlockedIncrement(&ref_);
}
Unlock();
}
return ext_obj_;
}
void MyObject::ReleaseExtentedObject(){
long release = InterlockedCompareExchange(&ref_, 0, 0);
long result = 0;
do{
result = release - 2;
} while ((result+2) != (release = InterlockedCompareExchange(&ref_, result, release)));
if(1 == result)
{
Lock();
if(1 == InterlockedCompareExchange((long*)&ref_, 0, 1))
{
if(NULL != ext_obj_)
{
delete ext_obj_;
ext_obj_ = NULL;
}
}
Unlock();
}
}
Some explanations:
I cannot use Boost. I'd like to but really cannot.
I use only CompareExchange and Incr / Decr on purpose. Don't ask.
I use first bit of ref_ to store the construction status (constructed / not constructed) and other bits for ref counting. It's the only way I found to manage 2 variables (ref counting and construction status) at the same time through atomic operations.
Some questions now :
Do you think it's 100% bullet proof?
Do you know some better solutions?
EDIT: Some have proposed to use shared_ptr. One up for a working solution with shared_ptr! Note that I need: lazy construction AND destruction when nobody no more use it.