unique_ptr<T, D>
is actually specially designed to be able to work with more arbitrary handle-like types. I spelled out the template name fully because D
is the key here. Normally unique_ptr<T, D>::get()
returns a T*
. That is the default, but this can be overridden by D
: the deleter type.
If the deleter type has a pointer
alias (D::pointer
is legal syntax), then unique_ptr<T, D>::get()
will return that type. This allows you to have something like unique_ptr<GLuint, gl::program_deleter>
, where gl::program_deleter::pointer
is of type int
.
I bring all of this up because shared_ptr
cannot do this. unique_ptr<T, D>
gets away with it because the deleter is actually part of the unique_ptr
type itself. By contrast, while shared_ptr
's constructors can take a deleter, the only thing that deleter function can do is delete the memory.
So shared_ptr<GLuint>::get()
will always return a GLuint*
. This means that, if you want to use shared_ptr
as some kind of shared handle type, that type must be dynamically allocated in some way. You may not be using the global heap, but it cannot just store and return integer either. shared_ptr<T>
always contains a T*
.
So no matter what, you're going to have to manage GLuint*
s if you want to use shared_ptr
's reference counting machinery. Yes, the deleter can be used to call glDeleteProgram
or whatever you want, but the shared_ptr<GLuint>
will still be storing a GLuint*
.
creating an OpenGL shader program handle does not actually involve heap memory
OK, let's forget for a moment that by creating an OpenGL object, the driver almost certainly heap allocated some memory. Let's look just at what you have to do.
By creating a shared_ptr
that owns some storage, something will be allocated. Namely, the shared block that manages the shared_ptr
's reference count. There's no getting around that. So if you want to use shared_ptr
's reference counting infrastructure, you're going to allocate from somewhere.
So the most idiomatic way to do this is to just heap allocate a GLuint
and use a special deleter that destroys the OpenGL object and deallocates the integer. It's not pretty and it's kind of wasteful, but it's hardly terrible. And if you use make_shared
, you can make things pretty compact in terms of allocations.
Now, you can avoid this allocation by cheating. You can do this:
GLuint program = glCreateProgram();
shared_ptr<GLuint> sp(reinterpret_cast<GLuint*>(program), ProgramDeleter);
So here, we're taking an integer and casting it to a pointer value, to be stored within the shared_ptr
. When you need to use it, you have to reverse the cast to recover the integer value.
But judge the following code for yourself:
glProgramUniform1i(reinterpret_cast<GLuint>(sp.get()), val);
Does that look like something you want to do frequently? Does it look like something you want to read frequently? Does that look like something that someone else will easily understand what's going on?
Not only that, you can never use *sp
to get the value, since the pointer value is the value in question.
Oh, and the reference counting control block still gets heap allocated, so it's not like you prevent allocating memory or something.
This is not idiomatic C++.
unique_ptr
andshared_ptr
for resources other than heap allocations.