Reputation: 61970
Windows handles are sometimes annoying to remember to clean up after (doing GDI with created pens and brushes is a great example). An RAII solution is great, but is it really that great making one full (Rule of Five) RAII class for each different type of handle? Of course not! The best I can see would be one full generic RAII class with other classes just defining what to do when the handle should be cleaned up, as well as other handle-specific aspects.
For example, a very simple module class could be defined like this (just an example):
struct Module {
Module() : handle_{nullptr} {}
Module(HMODULE hm) : handle_{hm, [](HMODULE h){FreeLibrary(h);}} {}
operator HMODULE() const {return handle_.get();}
private:
Handle<HMODULE> handle_;
};
That's all fine and dandy, and no destructor or anything is needed. Of course, though, being able to write the Handle
class to not need a destructor or anything as well would be nice, too. Why not use existing RAII techniques? One idea would be to use a smart pointer to a void
, but that won't work. Here's how the handles are actually declared under normal circumstances:
#define DECLARE_HANDLE(n) typedef struct n##__{int i;}*n
DECLARE_HANDLE(HACCEL);
DECLARE_HANDLE(HBITMAP);
DECLARE_HANDLE(HBRUSH);
...
It actually differentiates between handle types, which is good, but it makes using a smart pointer to void
impossible. What if, instead, since handles are, by definitions, pointers, the type could be extracted?
My question is whether the following is a safe assumption to make. It uses a handle to a desktop, which must be closed. Barring the differences between shared and unique pointers (e.g., FreeLibrary
has its own reference counting semantics), is assuming the handle is a pointer and making a smart pointer to whatever it's pointing to okay, or should I not use smart pointers and make Handle
implement the RAII aspects itself?
#include <memory>
#include <type_traits>
#include <utility>
#include <windows.h>
int main() {
using underlying_type = std::common_type<decltype(*std::declval<HDESK>())>::type;
std::shared_ptr<underlying_type> ptr{nullptr, [](HDESK desk){CloseDesktop(desk);}};
}
Upvotes: 3
Views: 817
Reputation: 129454
I believe all Windows pointers are technically pointers to internal objects inside the Windows kernel part of the system (or sometimes, possibly, to user-side objects allocated by the kernel code, or some variation on that theme).
I'm far from convinced that you should TREAT them as pointers tho'. They are only pointers in a purely technical perspective. They are no more "pointers" than C style "FILE *" is a pointer. I don't think you would suggest the use of shared_ptr<FILE*>
to deal with closing files later on.
Wrapping a handle into something that cleans it up later is by all means a good idea, but I don't think using smart pointer solutions is the right solution. Using a templated system which knows how to close the handle would be the ideal.
I suppose you also would need to deal with "I want to pass this handle from here to somewhere else" in some good way that works for all involved - e.g. you have a function that fetches resources in some way, and it returns handles to those resources - do you return an already wrapped object, and if so, how does the copy work?
What if you need to save a copy of a handle before using another one (e.g. save current pen, then set a custom one, then restore)?
Upvotes: 2
Reputation: 4659
I believe that both unique_ptr and shared_ptr allow you to provide custom deleter. Which I believe is just what you need to correctly manage lifetime of handles.
Upvotes: 1
Reputation: 3560
One approach you could take is to use a template class:
template<typename H, BOOL(WINAPI *Releaser)(H)>
class Handle
{
private:
H m_handle;
public:
Handle(H handle) : m_handle(handle) { }
~Handle() { (*Releaser)(m_handle); }
};
typedef Handle<HANDLE,&::CloseHandle> RAIIHANDLE;
typedef Handle<HMODULE,&::FreeLibrary> RAIIHMODULE;
typedef Handle<HDESK,&::CloseDesktop> RAIIHDESKTOP;
If there is a HANDLE
which isn't released by a function of type BOOL(WINAPI)(HANDLE)
, then you may have issues with this. If the releasing functions only differ by return type though, you could add that as a template parameter and still use this solution.
Upvotes: 2
Reputation: 70186
Technically, this ought to work perfectly well under all present versions of Windows, and it is hard to find a real reason against doing it (it is actually a very clever use of existing standard library functionality!), but I still don't like the idea because:
INVALID_HANDLE_VALUE
), or other side effects or behaviours that you maybe don't foresee now. Assuming that a handle has certain given properties may work fine for decades, but it may (in theory) mysteriously fail in some condition that you didn't think about. Admittedly, it is very unlikely to happen, but still it isn't 100% clean.Upvotes: 1