Using C++11 smart pointers for asset manager

Started by
22 comments, last by l0calh05t 10 years, 3 months ago

Also, quick question: can i attach a callback to the event of pointer cleanup, to call SDL_FreeTexture? Or there's no need for that?

Advertisement

The constructor of shared_ptr allows you to supply a Deleter, a function function object (that is, a class which has an operator() taking a pointer) which it will use to delete the object instead of calling delete.

Usually, this is used for using your own allocator, but nothing prevents you from putting any other code (such as SDL_FreeTexture) followed by ::delete into that function. I wouldn't do it personally (I'd rather call SDL_FreeTexture in the texture object's destructor because it makes me sleep more comfortably), but it is certainly possible to do either one.

The constructor of shared_ptr allows you to supply a Deleter, a function function object (that is, a class which has an operator() taking a pointer) which it will use to delete the object instead of calling delete.


That's not completely correct. The deleter must be a functor, that is if 'd' is an instance of Deleter then 'd(pointer)' must be a valid expression. So Deleter can be a normal function pointer type, a function object or even a lambda expression.

While we are talking about the deleter, here's one thing that surprised me in the past: The default deleter calls delete on the pointer provided to the constructor, using the type passed to the constructor, not the type specified in the shared_ptr template. This means that, even if you don't make the destructor of your base class virtual, shared_ptr can end up calling the correct destructor!

#include <iostream>
#include <memory>

struct Base {
  int x;
 
  Base(int x) : x(x) {
  }
 
  // Look, ma! No virtual destructor!
};

struct Derived : Base {
  Derived(int x) : Base(x) {
  }
 
  ~Derived() {
    std::cout << "A Derived with x=" << x << " is being destructed. Magic!\n";
  }
};
                                                                                                                                  
std::shared_ptr<Base> factory(int x) {                                                                                            
  return std::shared_ptr<Base>(new Derived(x));                                                                                   
}                                                                                                                                 
                                                                                                                                  
int main() {                                                                                                                      
  std::shared_ptr<Base> pointer = factory(4);                                                                                     
  std::cout << "So now I have a pointer to Base with x=" << pointer->x << '\n';                                                   
}                                                                                                                                 
Yes, that is an interesting property which was present even in the good old boost::shared_ptr. Actually, there was even a rather annoying bug with that in several 1.3x Boost versions: manually constructing the boost::shared_ptr out of the pointer had that behavior, but using boost::make_shared didn't. That was a source of a lot of fun under the right circumstances and a great sigh of relief throughout the universe when it was fixed.

So, the deleter is not a good idea? Better to wrap around the destructor?

It will probably work "just fine", but I don't like it because it interferes with my feeling of "correct".

A class should allocate the resources it's responsible for in the constructor and free them in the destructor. Never anything different, unless there is a really really really urgent reason. Here, there seems to be no urgent reason.

If you create an "asset" object (or whatever you call it) that manages a texture, it should free this texture when the object is destroyed. Or, it should send a message to the renderer, informing it that the texture is to be deleted at the end of the frame (maybe by registering an APC or the like). Or something else. But in any case, it should do something appropriate.

It shouldn't rely on the fact that some unrelated "manager" class is smart enough to remember doing this cleanup in a deleter function. Because maybe one day you rewrite it and forget to implement that detail. Or maybe one day you get some butt-head co-worker who thinks it's a clever idea to convert the obtained smart pointer to a raw pointer for "optimization". Or something else that you cannot foresee now. With cleanup happening in the destructor, you eleminate a possible point of failure.

Some time ago I wrote an asset manager based on boost smart pointers (essentially the same as C++11 smart pointers), so yes, it works. My approach was a little different though, as my manager only had weak pointers and the users had shared pointers, so as soon as there were no more users of a shared resource, it would be deallocated (the unused weak pointers would be discarded when a new resource is allocated).

@samoth: The advantage of a manager in this case is to prevent all kinds of objects loading the same resource again and again. The objects it manages however should behave in such a way that the destructor deallocates the resource.

This topic is closed to new replies.

Advertisement