(c++) Object copies and holding resources
I am having some trouble writing objects that hold a resource (open file, memory pointer, open GL texture object), which can be put into std:: containters without screwing up. The idea is that the object acquires the resource in the constructor and releases it in the destructor (pretty much like filestreams or autopointers), but when they are copied they should lose their resource and pass it on to the new object without re-allocating it. For example my texture object should, when copied, pass its texture id (gl texture object id) on to the new object and become invalid itself, but the new object should not reload and create the texture itself, it just receives an (already valid) texture id. This is needed mostly because i want to put these objects into containers in an efficient way and that generally involves a call to the copy constructor. Any clues or source code would be greatly appreciated.
A couple of thoughts:
- use containers of pointers-to-resource-holders ;->
- have the copy ctor and/or assignment operator release the resource reference held by the input object
- use reference counting ( see below )
// dynamic memory allocation version
class ResourceHolderRef
{
public:
ResourceHolderRef( Resource *const resource ) : m_resourceHolder( new ResourceHolder( resource ) ) {}
ResourceHolderRef( ResourceHolderRef const &orig ) :
m_resourceHolder( orig.m_resourceHolder ) { m_resourceHolder->AddRef(); }
~ResourceHolderRef() { m_resourceHolder->Release(); }
inline Resource* GetResource() const { return m_resourceHolder->GetResource(); }
private:
class ResourceHolder
{
public:
ResourceHolder( Resource *const resource ) : m_resource( resource ), m_refCnt( 1 ) {}
inline Resource* GetResource() const { return m_resource; }
inline void AddRef() { ++m_refCnt; }
inline void Release() { if( --m_refCnt == 0 ) delete m_resource; delete this; }
private:
~ResourceHolder() {}
Resource* m_resource;
int m_refCnt;
};
ResourceHolder* m_resourceHolder;
};
// static memory allocation version
class ResourceHolderRef
{
public:
ResourceHolderRef( Resource *const resource ) : m_resourceData( resource ), m_resourceHolder( &m_resourceData ) {}
ResourceHolderRef( ResourceHolderRef const &orig ) :
m_resourceHolder( orig.m_resourceHolder ) { m_resourceHolder->AddRef(); }
~ResourceHolderRef() { m_resourceHolder->Release(); }
inline Resource* GetResource() const { return m_resourceHolder->GetResource(); }
private:
class ResourceHolder
{
public:
ResourceHolder() : m_resource( 0 ), m_refCnt( 0 ) {}
ResourceHolder( Resource *const resource ) : m_resource( resource ), m_refCnt( 1 ) {}
inline Resource* GetResource() const { return m_resource; }
inline void AddRef() { ++m_refCnt; }
inline void Release() { if( --m_refCnt == 0 ) delete m_resource; }
private:
~ResourceHolder() {}
Resource* m_resource;
int m_refCnt;
};
ResourceHolder m_resourceData;
ResourceHolder* m_resourceHolder;
};
- use containers of pointers-to-resource-holders ;->
- have the copy ctor and/or assignment operator release the resource reference held by the input object
- use reference counting ( see below )
// dynamic memory allocation version
class ResourceHolderRef
{
public:
ResourceHolderRef( Resource *const resource ) : m_resourceHolder( new ResourceHolder( resource ) ) {}
ResourceHolderRef( ResourceHolderRef const &orig ) :
m_resourceHolder( orig.m_resourceHolder ) { m_resourceHolder->AddRef(); }
~ResourceHolderRef() { m_resourceHolder->Release(); }
inline Resource* GetResource() const { return m_resourceHolder->GetResource(); }
private:
class ResourceHolder
{
public:
ResourceHolder( Resource *const resource ) : m_resource( resource ), m_refCnt( 1 ) {}
inline Resource* GetResource() const { return m_resource; }
inline void AddRef() { ++m_refCnt; }
inline void Release() { if( --m_refCnt == 0 ) delete m_resource; delete this; }
private:
~ResourceHolder() {}
Resource* m_resource;
int m_refCnt;
};
ResourceHolder* m_resourceHolder;
};
// static memory allocation version
class ResourceHolderRef
{
public:
ResourceHolderRef( Resource *const resource ) : m_resourceData( resource ), m_resourceHolder( &m_resourceData ) {}
ResourceHolderRef( ResourceHolderRef const &orig ) :
m_resourceHolder( orig.m_resourceHolder ) { m_resourceHolder->AddRef(); }
~ResourceHolderRef() { m_resourceHolder->Release(); }
inline Resource* GetResource() const { return m_resourceHolder->GetResource(); }
private:
class ResourceHolder
{
public:
ResourceHolder() : m_resource( 0 ), m_refCnt( 0 ) {}
ResourceHolder( Resource *const resource ) : m_resource( resource ), m_refCnt( 1 ) {}
inline Resource* GetResource() const { return m_resource; }
inline void AddRef() { ++m_refCnt; }
inline void Release() { if( --m_refCnt == 0 ) delete m_resource; }
private:
~ResourceHolder() {}
Resource* m_resource;
int m_refCnt;
};
ResourceHolder m_resourceData;
ResourceHolder* m_resourceHolder;
};
Use boost::shared_ptr. It reference counts whatever its containing, and you use it like an auto_ptr. When the ref count goes to zero, it runs the resource''s destructor. When you move it in and out of containers you pay only the cost of adjusting the ref count.
I realize this is against the spirit of gamedev though, by suggesting third party libraries.
I realize this is against the spirit of gamedev though, by suggesting third party libraries.
An easier, often better solution is to disallow copying by making the copy constructor and operator= private and unimplemented.
Users can still pass that object around by reference.
Or, if you REALLY want ownership transfer, hold the resource via an std::auto_ptr. But I strongly advise against it.
Pass-by-reference is your friend.
[ Start Here ! | How To Ask Smart Questions | Recommended C++ Books | C++ FAQ Lite | Function Ptrs | CppTips Archive ]
[ Header Files | File Format Docs | LNK2001 | C++ STL Doc | STLPort | Free C++ IDE | Boost C++ Lib | MSVC6 Lib Fixes ]
Users can still pass that object around by reference.
class Foo{private: ResourceType some_resource; Foo( const Foo& ); Foo& operator=(const Foo&);};void Function1( Foo& );void Function2( Foo );Foo f;Function1( f ); // OkFunction2( f ); // Error, can''t copy.
Or, if you REALLY want ownership transfer, hold the resource via an std::auto_ptr. But I strongly advise against it.
Pass-by-reference is your friend.
[ Start Here ! | How To Ask Smart Questions | Recommended C++ Books | C++ FAQ Lite | Function Ptrs | CppTips Archive ]
[ Header Files | File Format Docs | LNK2001 | C++ STL Doc | STLPort | Free C++ IDE | Boost C++ Lib | MSVC6 Lib Fixes ]
Well, he said he wanted to put it in an STL container. STL uses references internally on a lot of implementations and you can''t have a reference to a reference currently in C++, so unfortunately references aren''t going to help here.
I know the separate-resource-object-with-ref-counter approach, but i am looking for a more lightweigt solution here: i do not want to have multiple object pointing at the same resource, i just want to be able to stuff my object into containers in an efficient way. i figured out this method:
The operator= would look similar to the copy constructor. I suppose having a private resource object is a little less error-prone since you can have multiple valid objects pointing at the same resource, but it requires more new/delete calls and more work, so i'll stick with this when it suffices.
Marijn
[edited by - marijnh on August 8, 2003 11:58:16 AM]
class TextureObject{public: TextureObject(const char* texturefile){ //Initialize the texture, save gl textureobject id to _id _valid = true; } TextureObject(const TextureObject& other){ _id = other._id; other._valid = false; _valid = true; } ~TextureObject(){ if (_valid) //De-allocate texture object } void bind(){ if (!_valid) throw horrible_exception(); //Set up openGL to use the textureobject _id }private: bool _valid; unsigned int _id;};
The operator= would look similar to the copy constructor. I suppose having a private resource object is a little less error-prone since you can have multiple valid objects pointing at the same resource, but it requires more new/delete calls and more work, so i'll stick with this when it suffices.
Marijn
[edited by - marijnh on August 8, 2003 11:58:16 AM]
quote:Original post by marijnh
I know the separate-resource-object-with-ref-counter approach, but i am looking for a more lightweigt solution here : i do not want to have multiple object pointing at the same resource, i just want to be able to stuff my object into containers in an efficient way .
quote:I realize this is against the spirit of gamedev though, by suggesting third party libraries.
*cough* Somebody called *that* one.
Lets think about this for a second: what''s more efficient:
1. adjusting a ref count (boost::shared_ptr)
2. copying the resources (which may or may not be trivial), flipping flags, *and* checking a flag on every operation.
Sorry but having a valid flag just sucks. If the object exists it is valid in my mind. The fact that a texture object might exist but no longer be valid is just silly, and the texture object should know NOTHING of implementation constraints like those.
If you''re so concerned about microoptimizations then just store pointers in the container and delete them at the appropriate time.
well, the kind of objects i am aiming at here is supposed to be one-of-a-kind, they will be kept in a container (put there by a manager object) and stay in there, other parts of the program use references to those in-container objects. thus invalid objects should not be a problem - only the temporary object that gets pushed into the container is invalid for a few instructions before it gets deleted. i am quite sure this is more efficient than creating a separate object on the heap with a refcount. i know my solution is not idiot-proof, but it will do.
Marijn
edit: really confusing typo
[edited by - marijnh on August 8, 2003 12:59:20 PM]
Marijn
edit: really confusing typo
[edited by - marijnh on August 8, 2003 12:59:20 PM]
I still think putting pointers into the std container is the way to go...why can''t you do this?
Putting pointers in containers is the easiest way to go, but that leaves you responsible for cleaning up the objects and you have to make sure not to copy them. I''ve ended up using a reference-counting ''sub-object'' inside these objects, thus i can treat them as normal self-destroying, safe copying object without risk of leaking memory.
The approach i showed above had a big mess up - i change something in an object referenced by a const reference, since the default copy-constructor takes a const reference. This can be solved with a const_cast, but that''s not very nice coding.
Marijn
The approach i showed above had a big mess up - i change something in an object referenced by a const reference, since the default copy-constructor takes a const reference. This can be solved with a const_cast, but that''s not very nice coding.
Marijn
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement