# Using C++11 smart pointers for asset manager

This topic is 1464 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

So, i'm working on a small asset manager for my engine, and i'm using type inference and smart pointers from C++ to add the assets to an unordered_map. I add a string and the smart pointer to this set:

assets.insert(make_pair('my-bitmap', make_shared<Bitmap>(100,100)));

This assets is a member of the AssetsManager, which it's single instance is stored in a member of the Scene object. Is that a good practice? Is this more efficient than another solution, since i don't have to work on pointers manually? Also, what happens when i remove the Scene object?

##### Share on other sites

Is that a good practice?

It seems OK to me.

Is this more efficient than another solution, since i don't have to work on pointers manually?

No. Why would it more efficient? The point of smart pointers is that it is easier to get things right. It actually generally sacrifices a bit of efficiency in the process. But that's OK.

Also, what happens when i remove the Scene object?

The shared pointer gets destroyed, which means that the reference counter will be decremented. If it reaches 0 (which means no one else is using this resource), the Bitmap will be destroyed and its memory released.

##### Share on other sites

So, it should work like a charm? Amazing!

##### Share on other sites

Why are you using shared_ptr?

To me it seems "natural" that an asset manager "owns" the assets exclusively. It seems to be strange that an asset can outlive the manager, so no shared ownership should be required.

Having said this, unique_ptr is the more obvious solution to your problem.

Do not use shared_ptr unless you really need shared ownership, especially if efficiency is your concern.

##### Share on other sites

Why are you using shared_ptr?

To me it seems "natural" that an asset manager "owns" the assets exclusively. It seems to be strange that an asset can outlive the manager, so no shared ownership should be required.

Having said this, unique_ptr is the more obvious solution to your problem.

Do not use shared_ptr unless you really need shared ownership, especially if efficiency is your concern.

I disagree.

Having a shared_ptr in asset manager and handing out weak_ptr to asset users is a solid strategy.

##### Share on other sites

[Thats reply to cdoubleplusgood's post, in the meantime someone posted so just want to clarify as I didn't quote the original post ;)]

Question is - whats his plan to retrieve assets from manager? If he uses unique_ptr, he shouldn't really store any reference/pointer so he has to query manager every time he uses an asset, which will probably be every frame. He can't really pre-fetch assets that he uses and release them when he's done.

Also, when using shared_ptr manager knows when its the only owner left and can release resource (or keep it for some time before releasing completly).

How would unique_ptr be used in such pipeline?

Edited by noizex

##### Share on other sites

Having a shared_ptr in asset manager and handing out weak_ptr to asset users is a solid strategy.

I don't understand this strategy. Why weak_ptr? The users will have to lock and check for expiration everytime using the asset. If you already have shared_ptr in the manager, why not for the users?

##### Share on other sites

[Thats reply to cdoubleplusgood's post, in the meantime someone posted so just want to clarify as I didn't quote the original post ;)]

Question is - whats his plan to retrieve assets from manager? If he uses unique_ptr, he shouldn't really store any reference/pointer so he has to query manager every time he uses an asset, which will probably be every frame. He can't really pre-fetch assets that he uses and release them when he's done.

Also, when using shared_ptr manager knows when its the only owner left and can release resource (or keep it for some time before releasing completly).

How would unique_ptr be used in such pipeline?

I don't know what the OP's manager actually does and what he wants to achieve. Of course, using shared_ptr for automatic resource management can be very convenient. However, he asked for efficiency, and shared_ptr is not the most efficient way to maintain pointers, especially because shared_ptr is thread safe even if you don't need it: The reference counters are always synchronized.

##### Share on other sites

Having a shared_ptr in asset manager and handing out weak_ptr to asset users is a solid strategy.

I don't understand this strategy. Why weak_ptr? The users will have to lock and check for expiration everytime using the asset. If you already have shared_ptr in the manager, why not for the users?

Because they're users. Giving out only weak_ptr's opens up the possibility to, f.ex. unload some resources that aren't actually being used in favor of some other high priority resources. This would need some mechnanism to avoid thrashing, tho. If you used shared_ptr instead, you wouldn't have the possibility to track actual usage.

##### Share on other sites

Of course, using shared_ptr for automatic resource management can be very convenient. However, he asked for efficiency, and shared_ptr is not the most efficient way to maintain pointers, especially because shared_ptr is thread safe even if you don't need it: The reference counters are always synchronized.
This is certainly correct, and something to always keep in mind when copying around shared pointers. Passing around a shared or weak pointer is not close to a no-op as compared to passing around a raw pointer.

However, in the context of an asset manager you need to consider that the rather slow atomic increments/decrements used in the shared pointer are entirely neglegible compared to only a single disk access for loading an asset. We're talking of 30-50 nanoseconds versus 8-10 milliseconds.

Formally, giving out a weak pointer as suggested by Mnemotic is the correct thing to do (though giving out shared pointers would probably work just fine 90% of the time too), seeing how the manager owns the resources and should be allowed to toss them on a as-needed base, but safely so no resources "disappear" while in use.

##### Share on other sites

I can't see how a solution that passes weak pointers would work in the case of multi-threaded code. In single-threaded code, I can check if my weak pointer is still valid before I use it, and I know that the object is not going away while I am using it. How do I get such a guarantee in multi-threaded code?

##### Share on other sites

C++11 smart pointers are thread-safe. So sayeth the Standard.

##### Share on other sites

I can't see how a solution that passes weak pointers would work in the case of multi-threaded code. In single-threaded code, I can check if my weak pointer is still valid before I use it, and I know that the object is not going away while I am using it. How do I get such a guarantee in multi-threaded code?

You're locking weak_ptr before use, which creates shared_ptr<> temporarily, so you have guarantee that it won't go away in that scope. Thats my understanding at least.

##### Share on other sites

[Thats reply to cdoubleplusgood's post, in the meantime someone posted so just want to clarify as I didn't quote the original post ;)]

Question is - whats his plan to retrieve assets from manager? If he uses unique_ptr, he shouldn't really store any reference/pointer so he has to query manager every time he uses an asset, which will probably be every frame. He can't really pre-fetch assets that he uses and release them when he's done.

Also, when using shared_ptr manager knows when its the only owner left and can release resource (or keep it for some time before releasing completly).

How would unique_ptr be used in such pipeline?

Supposedly he would then be handing out raw pointers from the manager. Which is fine, it's usage of new/delete that's discouraged, not raw pointers.

##### Share on other sites

You're locking weak_ptr before use, which creates shared_ptr<> temporarily, so you have guarantee that it won't go away in that scope. Thats my understanding at least.

Yes. You create a shared_ptr from the weak_ptr using lock, then you test if the shared_ptr is !nullptr.

##### Share on other sites

I can't see how a solution that passes weak pointers would work in the case of multi-threaded code

It works by the combination of these two:

You're locking weak_ptr before use, which creates shared_ptr<> temporarily, so you have guarantee that it won't go away in that scope.

C++11 smart pointers are thread-safe. So sayeth the Standard.

Although the latter is a bit of a "relaxed" wording, it's close enough.

You are explicitly allowed to call all functions including copy constructor of a shared_ptr concurrently and you are guaranteed that the reference counter of a shared_ptr is at all times correct/consistent. That doesn't guarantee that the object still exists when/while you copy the shared pointer, of course. It does guarantee that the result is well-defined, however.

Locking a weak_ptr creates a copy of the shared_ptr which may be an empty shared pointer (if the reference count has gone to zero in the mean time and the object was destroyed). So you have no guarantee that your operation will succeed, but you are guaranteed that the result is verifiable. If the copied shared pointer is nullptr, locking the weak pointer failed and the object is gone. Otherwise you now hold a (temporary) shared_ptr and it's guaranteed that the object will live for as long as you hold the temporary.

Edited by samoth

##### Share on other sites

Just giving some info to the discussion: I'm storing SDL_Texture's from calls made to IMG_Load and SDL_CreateTextureFromSurface subsequently. I fetch the textures from there to render with SDL_RenderCopyEx. I'm also storing Mix_Chunk and Mix_Music.

##### Share on other sites

I find it a bit curious he used the phrasing "small asset manager" and suddenly we're all rambling about efficiency and what is pragmatically correct and it being multi-threaded and.. goodness do we even know if he cares about any of this stuff?

To be perfectly blunt performance loss from the internals of your asset manager are probably going to make an almost completely negligible effect on performance, the only important thing here is that you're not reloading a resource from disk unless you absolutely have to, how that works entirely depends on your game. For some games it makes sense to stream-load resources and unload a lot, for others you might just be sticking a bunch of junk in at the start of a level or something.

In the end even the naive approach of just loading everything at the start works fine for a great majority of games. Whats most important is you figure out what the thing needs to do to be easily usable from the outside.

##### Share on other sites

Actually, i care about all those concerns. I might not implement strategy A or strategy B, but i would like to know them both. While my current project does not plan to multi-threaded, i would love to hear the opinion of more experienced developers towards my code and which challenges i might face with it. I'm enjoying the discussion, thanks everyone for all the insights!

##### Share on other sites

I think I would start with shared pointers: It looks like it's the easiest solution to get right, and it should cover most situations. It's good to know about weak_ptr<T>::lock(), but it seems cumbersome.

##### Share on other sites

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?

##### Share on other sites

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.

Edited by samoth

##### Share on other sites

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.

##### Share on other sites

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';
}