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
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.
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
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
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:
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++.