preventing: delete &refToSomething

Started by
7 comments, last by GameDev.net 17 years, 10 months ago
Hello, I have a Manager class that deals with Resources. I want to prevent the 'user' form using Ressources through pointers directly (new/delete), so how good is it to make a sort of: (veri basic pseudo-example)

const Resource& CreateResource(int resId)
{
    Resource* pRes = new Resource(resId);
    
    if(failure)
        return Resource::nullResource;
    else
        return *pRes;
}
- The first point is that no resource made available to the user should ever be null. You can think of nullResource as a 1x1 transparent texture for example - The second point is that it delegates the resource management to the Manager class. Yet I wondered how enforce the (basic idiot) user not to break the system's rule and call a:

const Resource& cRef = CreateResource(0x001122333);
delete &cRef
I've tried this with a basic struct example at home and it does work perfectly. At first I thought that the const keyword would prevent one to call delete on it, but that seems not to be the case. (I havent deeply thought of what no-delete-on-const-ptr would imply in a larger scale, though) ( - little digression - or is it that the & operator applied to a const reference doest yield a const pointer ?) The only thing I've came up with is overloading operator delete without actually changing it, put placing it in the private access. Any thoughts about that ?
Advertisement
I'd just not worry about it. If the user wants to cripple things by behaving badly, there's not much you can do to stop it. Providing good documentation about how to use it and get the best experience is really the best way.

I don't think overriding delete in the resource class will help. That'd work for methods of Resource calling delete, but not ouside the class. Plus, user could always just call ::operator delete(ptr) anyway.
[sub]My spoon is too big.[/sub]
Yup you're quite true. But in fact, like most question I post here to GDNet, this one was curiosity-driven rather that need-driven, if you know what I mean :) So any other thoughs are still welcome !

Cheers,
Janta.
Consider having your manager class return a smart pointer, such as boost::/std::tr1::shared_ptr<>, to your resource instead of a reference. This both conveys semantically that the resource should not be freed by the client and reduces the chances of the resource not being properly freed.
Making the destructor private is also an option, though you will have to make the manager class a friend, so that it can properly dispose of the object. It will also prevent you from creating local variables of that type.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Thats a good point, since a "Resource& res" could easily turn into a "Resource res", with the compiler barely noticing in some cases.

But now I've thought more about it, I'm questionning the point of returning references rather than just pointers, assuming that I cant prevent the end user from doing something stupid anyway. The delete and new could still be kept private, instanciation/delete could still be delegated tasks of the manager, and all Resource* pointers would be obtained through the same kind of "factory pattern" (not sure the name is really appropriate though), thus preventing them from ever be NULL

If my Resources were ref-counted, then handling pointer or references would be almost as bad. I mean with a pointer, a programmer could just set it to NULL or let it go out of scope without decrementing the ref count, but the problem would be the same with a reference (although I guess that with a reference, a programmer is not undergoing the temptation of deleting or "=NULL"-ing)

Thoughts thoughts thoughts... I never see the end of them. In addition, boost's smart pointers (which I had considered in fact) seem to be a very good solution too (though I tend to like gettings my hands dirty)

I appreciate your replies so far :)
Janta
My thoughts:

- Don't return references to resources. This is a pain in the butt for the user. You can solve the "null resource is a valid 1x1 resource" issue just the same with pointers. Returning references means
TextureResource &tex = texMgr->GetTexture("foo");  if(!tex.CanBeRenderTarget()) {    // Oops, try a different texture.    tex = texMgr->GetTexture("foo2");  }  // use tex as a render target

does not do want you probably want, since references cannot be reseated. One part of resource management is given the resource "handles" (whatever they are) value semanatics so that the user can pay more attention to the use of the resource, rather than the management of the lifetime of its variable.

- boost::shared_ptr<> works wonderfully for this. Typedefs like:
typedef boost::shared_ptr< Texture> TextureH;

allow the texture handles to be treated basically as value types (excepting the use of operator-> to access them, which I prefer to . anyway, visually). The reference counting is done for you (no need to manually have users add or recall references), and if you ever want to switch to a different wrapper than shared_ptr, all you need to supply is the appropriate operator-> and the rest of your code should work fine (unless you invoke shared_ptr member functions anywhere). I've had success with a lightweight wrapper around shared_ptr as well (to prevent invocation of undesired shared_ptr methods as well as some other things).

- There's a point at which you can't stop the user from doing really stupid stuff. And well before that point, you'll be optimizing for the rare case when the user is doing something slightly stupid, and that's a waste of your time. If you are not afraid of the friend keyword, you could make your constructors and operator= private (leave the destructor public) and designate friendship to the manager. That way the user cannot dereference your pointer/handle/what have you an obtain a regular Resource from a Resource* (etc), at it would invoke the copy ct or operator= and cause a compiler error.
you could also think about not giving the pointer/reference to the user in the first place. you could give the user an (otherwise meaningless) int instead. then the user has to invoke methods of the instance in question via the resource managed by passing that ID like
resource_mgr->do_something(id0, parameters_here);
basically you 'd tell the managed to do something with a given instance identified by id0.
i think that in (user created) game code this might actually be worth it. this could also be helpful if you're planning on introducing multi-threaded processing where you couldn't risk handing out pointers. using the above approach making sure everything is done in a synchronized way could happen at a central location.
just some thoughts, though.
i 've been talking about the resource manageR .. i don't know why i kept hitting 'd' so much..

This topic is closed to new replies.

Advertisement