2
\$\begingroup\$

In an OpenGL application that I am writing, I want to have a simple shader class to wrap the OpenGL shader handle. Ultimately, I want this shader class to behave very similarly to a shared_ptr in c++ (that is, keep a reference count and free the resource when no references are left). While it is relatively trivial to implement this reference counting from scratch, I was wondering if it is considered a better design choice to instead use std::shared_ptr with a custom deleter to free up the resource.

The main source of my doubt is the fact that it might be considered unconventional due to the fact that (as far as I know), creating an OpenGL shader program handle does not actually involve heap memory (which is what shared_ptr takes care of) but I still feel that it could apply here because I want this resource to be handled very similarly to how heap memory would be handled. The purpose of this question is basically to seek the opinion of others as to whether this actually is unconventional, because I do not know.

Also, although I used shaders as an example, the same question also applies to textures and buffers in OpenGL as they also must be allocated and freed.

\$\endgroup\$
1
\$\begingroup\$

There is generally nothing wrong with using shared_ptr for that purpose. You would RAII the OpenGL handle (to the shader/buffer/texture) in a typical way, and let shared_ptr manage the object's lifetime. Everything works, forget how it works.

Of course, shared_ptr does things that you do not necessarily need or want, and those are not free. Notably, the reference count is maintained atomically. Which is effectively zero overhead and totally unimportant if you create half a dozen references per frame. Who cares!

On the other hand, if you create some ten thousand references per frame, the atomic increments/decrements may very well become a dominating factor by saturating memory bandwidth, and there's very little you can do about it.

Also, one has to consider whether automatic management is really what's needed. shared_ptr is not really just "reference count and too lazy to check". It is intended to share ownership (as the name implies!). If that's what you do, it's the right tool. If it's not what you actually do, then you may use it anyway but there may be better designs. It is tempting since you can create weak references, and stuff "just works" even if you don't know when something is needed, but in a scenario where you know exactly whether or not something will be needed for the next frame, it is possibly sub-optimal.

You might find that using a unique_ptr or even raw pointers may mean maybe some extra management work, but not really all that much, and is zero overhead in total. Note that from a logic point of view, a function that e.g. draws from a buffer does not own it. The resource management system does. If a function does not own something, then passing a (const) raw pointer is just fine. It's actually "more correct" than passing a shared one since you do not communicate something that isn't true.

Will shared_ptr work? By all means. If that's all that counts, please use it, do not reimplement it yourself.

\$\endgroup\$
1
\$\begingroup\$

shared_ptr will work fine holding your buffers/texture/shader resources if you really need shared ownership of those resources. I'd like to argue the case (or underscore it, I guess, since others have touched on it) that shared ownership is not what you want for these resources. It just doesn't make sense for them.

Somewhere in your code is a class or other module of code, usually called a GraphicsDevice or similar, that is responsible for actually creating these resources and handing out the shared_ptrs to them. By using shared_ptr, you are declaring that the ownership for those resources is now shared between GraphicsDevice and whoever gets a shared_ptr to a reference.

That's an abstraction that you cannot make true, in reality. Once the GraphicsDevice goes away (once you shut down OpenGL), any outstanding shared_ptrs to those resources are still "alive." From the perspective of the client code, that's fine, because you said the client code can share ownership of that resource. But in practice the resource is now a zombie, and it cannot even be deleted safely, since you've shut down the graphics API.

This is illogical. It leads to a general inability to destroy resources (since no one place can do so with authority), and to crashes on shutdown as things release zombie references to graphics resources after the graphics system is gone.

Consider instead an approach where you hand out non-owning references to the resources. A bare pointer makes more logical sense (although doesn't prevent dangling reference crashes). An index/version handle like the kind used in slot map implementations can be even better, since any such references left dangling cannot cause a crash since you they can no longer be used to resolve to the actual resource at all.

\$\endgroup\$
0
\$\begingroup\$

std::shared_ptr, although named this way, it not intended only for dynamic memory mangement. As per the cppreference, it

retains shared ownership of an object through a pointer

Note the usage of the word object rather than memory block. This means that a std::shared_ptr is a tool for general resource management, with a very common application being memory management.

Support for the management of other resource types of offered by having constructors that take a different deleter, to substitute the standard delete call, for example.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.