Reputation: 3417
Say I have this class called Dog. Every dog has a different name but the same barking voice (which is loaded from a resource file).
class Dog {
public:
Dog(const string &name) : _name(name) {
_barkingVoice.load();
}
~Dog() {
_barkingVoice.free();
}
string getName() const { return _name; }
void bark() { _barkingVoice.play(); }
private:
string _name;
VoiceResource _barkingVoice;
};
I want to call _barkingVoice.load()
only if the instance of Dog is the first one, and _barkingVoice.free()
only if there are no more instances of Dog.
The obvious solution is to set _barkingVoice as static and keep a reference counter of Dog as a data member.
My question is if there's an easier way to do this. Maybe an std implementation or something like that.
Upvotes: 3
Views: 434
Reputation: 42838
Make a reusable class to encapsulate the reference counting:
template<class ResourceType, class OwnerType>
class RefCounted {
public:
RefCounted() { if (++_refCount == 1) _resource.load(); }
virtual ~RefCounted() { if (--_refCount == 0) _resource.free(); }
ResourceType& operator*() { return _resource; }
ResourceType* operator->() { return &_resource; }
private:
static unsigned _refCount;
static ResourceType _resource;
};
template<class T, class U> unsigned RefCounted<T, U>::_refCount = 0;
template<class T, class U> T RefCounted<T, U>::_resource;
class Dog {
public:
Dog(const string &name) : _name(name) { }
string getName() const { return _name; }
void bark() { _barkingVoice->play(); }
private:
string _name;
RefCounted<VoiceResource, Dog> _barkingVoice;
};
Every template instantiation will have their own _refCount
and _resource
.
The second template parameter is to handle cases where you instantiate RefCounted
with the same ResourceType
but want to have separate reference counting for those instantiations. E.g. if you add a Cat
class and want it to have its own Refcounted<VoiceResource>
:
class Cat {
// ...
private:
RefCounted<VoiceResource, Cat> _meowingVoice;
};
Upvotes: 2
Reputation: 1124
First, why VoiceResource
is not static? If it's shared between all instances of Dog
, it should be. Else, you will need to load or copy the resuorce at every constructor call.
Have a static variable static int instanceCount;
, that is set to 0
. In every casual, copy and move (C++11) constructor increment it, in destructor decrement it. That will give you opportunity to do what you wanted.
That will basicly work like shared_ptr<T>
does, the might be a way to use it here instead of writing your own code, I just can't figure that out.
class Dog {
public:
Dog(const string &name) : _name(name) {
loadResource();
}
Dog(const Dog& b) : name(b.name) {
loadResource();
}
// only C++11:
Dog(Dog&& b) : name(std::move(b.name)) {
loadResource();
}
~Dog() {
freeResource();
_barkingVoice.free();
}
string getName() const { return _name; }
void bark() { _barkingVoice.play(); }
private:
string _name;
static VoiceResource _barkingVoice;
static int instanceCount;
static void loadResource() {
if (instanceCount == 0) {
_barkingVoice.load();
}
++instanceCount;
}
static void freeResource() {
--instanceCount;
if (instanceCount == 0) {
_barkingVoice.free();
}
}
};
int Dog::instanceCount = 0;
Upvotes: 2
Reputation: 42838
Make _barkingVoice
a std::shared_ptr<VoiceResource>
.
A shared_ptr
does exactly what you need: uses references counting to track the deletion of the last object, when it will deallocate the resource.
Upvotes: 1