|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWindows resources, like GDI handles, are hard to manage correctly without
creating leaks. This article shows how smart pointers can simplify this, and
almost eliminate a major source of errors. It continues my previous article,
Smart Pointers to boost your code,
providing a practical example for using Update: While the first version was more or less like "concept art", I
brushed up the sources a bit to make the more useful. I added a
Contents
BackgroundMany resource handles encountered in Win32 programming do not fit an object-oriented environment very well. Here is a list of problems:
If you have a function returning such a handle, you must at least specify if and when to release the handle. Things get much more complicated if the handle is a member of the class. Take a look at this innocent code snippet: class CMessage { protected: HFONT m_font; public: ~CMessage(); // ... }; CMessage CreateMessage(CString const & msgText, LPCTSTR fontName, int fontSize); Code Snippet (1): Demonstrating the problem Let me ask one question: Should the destructor of
One way or another, the user of the class is concerned with managing your
A multitude of solutions exist, some are simple, some not:
The nature of handles makes them prime candidates for a reference counting mechanism:
Further, we would like to keep the possibility of "unmanaged" handles (i.e., it is not deleted automatically). This flag should be set when the smart pointer is created (close to where we acquire the resource, because this is the place where we know how to treat it). See below: Why Not MFC?, why the MFC solution doesn't do it for me. Smart Pointers to RescueAs seen above, a reference counting smart pointer is ideal to handle Handle. Why
Note: I mentioned custom deleters only briefly (if at all) in my previous article. See the boost documentation for more. Let's look at the example for an // first we need a custom deleter for the font: void delete_HFONT(HFONT * p) { _ASSERTE(p != NULL); // boost says you don't need this, // but lets play safe... DeleteObject(*pFont); // delete the Windows FONT resource delete pFont; // delete the object itself // (normally done by default deleter) }; // typedef for our smart pointer: typedef boost::shared_ptr<HFONT> CFontPtr; // and a simple "Create" function: CFontPtr CreateFontPtr(HFONT font, bool deleteOnRelease) { if (deleteOnRelease) { // construct a new FontPtr. the custom deleter // is specified as second argument return CFontPtr(new HFONT(font), delete_HFONT); // (A) } else { // construct a new FontPtr with the default deleter: return CFontPtr(new HFONT(font)); } } Code Snippet (2): The initial idea The line (A) is the "magic" one: here we initialize the Now, we can make heavy use of Do you remember the first rule for using smart pointers? Put the resource into a smart pointer as soon as you get it. This rule holds up here as well: because when we get the font handle, we know exactly if it should be deleted or not. The smart pointer will carry around this flag, and automatically "do what we want". There are some problems even this solution cannot solve:
But such is life in C++, you are always free to shoot yourself in the foot you like most. Some problems, however, can be solved better:
But this is the question of the next part. Developing a complete solutionThis paragraph shows how to turn the concept into a complete extensible solution. It might be helpful in understanding how template libraries evolve into the complex beasts they are. The following goals were set:
EncapsulationFirst, we encapsulate the details of the smart pointer, solving the first two requirements: class CFontRef { protected: typedef boost::shared_ptr<HFONT> tBoostSP; tBoostSP m_ptr; public: explicit CFontRef(HFONT font, bool deleteOnRelease) { if (deleteOnRelease) m_ptr = tBoostSP(new HFONT(font), delete_HFONT); else m_ptr = tBoostSP(new HFONT(font)); } operator HFONT() const { return m_ptr ? *m_ptr : NULL; } }; Code Snippet (3): Moving to a specialized class You might (or should) notice the following things:
Another note: Now would be a good time to make the simplification used below. However, I'd like to go the "safe & clean way" a bit further. Second, we can make both the Handle type and the Deleter a template
parameter. It's simple for the Handle, but some compilers can't handle a
function as template argument. The standard solution is to turn the Deleter
function into a functor - that is, a class that overloads
template <typename HDL, typename DELETER> class CHandleRefT { protected: typedef boost::shared_ptr<HDL> tBoostSP; tBoostSP m_ptr; public: CHandleRefT(HDL h, bool deleteOnRelease) { if (deleteOnRelease) m_ptr = tBoostSP(new HDL(h), DELETER()); else m_ptr = tBoostSP(new HDL(h)); } operator HDL() const { return m_ptr ? *m_ptr : NULL; } }; // the font deleter, turned into a functor: struct CDeleterHFONT { void operator()(HFONT * p) { DeleteObject(*p); delete p; } }; // the typedef for our CFontRef: typedef CHandleRefT<HFONT, CDeleterHFONT> CFontRef; Code snippet (4): turning the class into a template
template <typename GDIHANDLE> struct CDeleter_GDIObject { void operator()(GDIHANDLE * p) { DeleteObject(*p); delete p; } }; // the typedef now has a nested template: typedef CHandleRef<HFONT, CDeleter_GDIObject<HFONT> > CFontRef; Code snippet (5): a helper template Specialized, more lightweight implementationThere are two things still bugging me:
The first problem could be solved with another helper template, but that
wouldturn the actual type of CFontRef into something like
However, both solve themselves when using some internal knowledge about
Windows: all resource handles in Windows can be represented by a So, with the following code, we can go: // a void deleter replaces the default deleter for "unmanaged" handles struct CDeleter_Void { void operator()(void *) {} } template <typename HDL, typename DELETER> class CHandleRef { protected: typedef boost::shared_ptr<void> tBoostSP; tBoostSP m_ptr; public: explicit CHandleRef(HDL h, bool deleteOnRelease) { if (deleteOnRelease) m_ptr = tBoostSP(h, DELETER()); else m_ptr = tBoostSP(h, CDeleter_Void()); } operator HDL() const { return (HDL) m_ptr.get(); } }; struct CDeleter_GDIObject { void operator()(void * p) { DeleteObject( (HGDIOBJ) p); } }; typedef CHandleRef<HFONT, CDeleter_GDIObject> CFontRef; Code snippet (6): the final code Before, the smart pointer stored a pointer to our resource handle. Now, we
store the resource handle directly in the smart pointer (represented as
Using the solution(I'll repeat a few things here for the impatient who skipped over all that boring explanation stuff)
Template parameters:
Automatic vs. manual handlesWhen constructing a For an automatic handle, assign the raw handle to a For a manual handle, that you will delete yourself manually, or that must not
be deleted, specify The idea here is that you can pass around, copy, store, or return a
GuidelinesUtility functions that merely receive and use a resource handle (without
storing it) may use the raw handle as argument. However, when the Handle is
stored (e.g., as a class member) or used as a return value from a function, a
As an example, the Constructor
Initializes a new
Releases the image list. To add support for a new type:The type must be convertible to and from
Why not MFC?MFC tried to solve the problem with its own wrappers. Unfortunately, they
chose to wrap the first option from the "possible solutions" above: no copy
functionality, but they pass around
When turning UI code into general routines and classes, I regularly stumble over all these four scenarios. It is not a pleasant experience, I can tell you - so I end up using raw resource handles instead, to have "only the normal problems". Further, I like UI code not to be dependent on MFC, if possible. The best code IMO is a library that relies solely on the Win32 API interface, and does not put any requirements on the framework. CDCRef for Device ContextsThe Device Contexts are the most complicated resource I've come across: There are
two cleanup functions: CDCRef is implemented as separate class, since I didn't want the complexity of HDC to "creep" into the Other HandleRef classes. The main difference is the constructor - instead of a flag, you directly pass the deleter: CDCRef(hdc, CDCRef::Null()) // a CDCRef that doesn't get released automatically CDCRef(hdc, CDCRef::Delete()) // a CDCRef to be released by DeleteDC CDCRef(hdc, CDCRef::Release(hwnd)) // a CDCRef to be released by ReleaseDC For ReleaseDC, the HWND is passed as parameter to the deleter. Again, we associated all information required for cleanup with the object when it is constructed, which is a design principle of HandleRef. Further, CDCRef provides static member functions that wrap the Win32 API
functions that acquire a DiscussionThe solution provided here is not new. The idea of the article is to show
that the solution becomes very simple when using an appropriate library (about
20 lines of actual code), and how such a solution would be developed from the
initial idea to use Now, How good is the solution?
The second is not an issue: smart pointers are such a fundamental technique
that you shouldn't be caught without. The last might be an issue when handling
lots of resources. However, such an application likely benefits most from
automated resource management, and you can keep performance under control with
using a raw handle (or a The first actually reflects a design choice: CHandleRef does not isolate you from the underlying API, but makes it transparent and easier to use. (MFC, in contrast, isolates you very well in the default case, and fails misderably in all others). Additional advantage: CHandleRef is not only library independent, it also integrates easily with other libraries. Is a "better" solution possible? Definitely. You still can do things wrong: release an object while it is still selected into a device context, specify the wrong cleanup policy, or delete the handle while it is still in use. However, a completely safe solution would require to wrap the entire GDI API: all functions creating or using an GDI object.
To make Conclusion and Thank YouThank you all for the encouraging feedback! It is great to see the time to
write the article was well spent. For a more formal conclusion: We have seen that
| ||||||||||||||||||||