Introduction
Microsoft Visual Studio 2003 supports new advanced C++ features which were not available in the previous VS versions. Template template parameters, the powerful tool for generic C++ programming, is one of them. This article describes creating of Smart Handle template class that uses the new template template parameters.
The EnsureCleanup template class from [1] is a thin wrapper around the Windows handles, which allows to create various kinds of handles (HANDLE, HKEY etc.) and releases them in the destructor. The template is instantiated with a pointer to a release function - CloseHandle, RegCloseKey etc. and an invalid handle value - INVALID_HANDLE_VALUE for the file handle, NULL for all other handles. Using the Policy-Based Design described in [2], we can replace a pointer to a release function with the Release Policy , which is a template class itself. This policy is responsible for releasing the handle in the class destructor. Replacing the C-style function pointer with a Release Policy class makes the code more generic and gives more flexibility to the Smart Handle class. As an example of such flexibility, we can use this class as simple smart pointer, providing an appropriate Release Policy.
WARNING: this code is compiled only on Visual Studio .NET 2003. It uses features unsupported in previous Visual Studio versions.
Class Design
Let's decide what the class CSmartHandle looks like. It should work with the following Windows handles types:
| Handle |
Type |
Release Function |
Invalid Value |
| General Handle |
HANDLE |
CloseHandle |
NULL |
| Registry Key Handle |
HKEY |
RegCloseKey |
NULL |
| Dll Handle |
HANDLE |
FreeLibrary |
NULL |
| View of File Handle |
PVOID |
UnmapViewOfFile |
NULL |
| File Handle |
HANDLE |
CloseHandle |
INVALID_HANDLE_VALUE |
Each Release Policy class is a template class with a Handle Type parameter. The Close function is responsible for releasing the handle:
template <typename T>
struct CCloseHandle
{
void Close(T handle)
{
CloseHandle(handle);
}
};
template <typename T>
struct CCloseRegKey
{
void Close(T handle)
{
RegCloseKey(handle);
}
};
template <typename T>
struct SCloseLibrary
{
void Close(T handle)
{
FreeLibrary(handle);
}
};
template <typename T>
struct CCloseViewOfFile
{
void Close(T handle)
{
UnmapViewOfFile(handle);
}
};
Exactly as done in [1], let's define some number of typedef for template clients (assuming that the third template parameter has a default value of NULL):
typedef CSmartHandle<HANDLE, CCloseHandle> CAutoGeneralHandle;
typedef CSmartHandle<HKEY, CCloseRegKey> CAutoRegKey;
typedef CSmartHandle<PVOID, CCloseViewOfFile> CAutoViewOfFile;
typedef CSmartHandle<HMODULE, SCloseLibrary> CAutoLibrary;
typedef CSmartHandle<HANDLE, CCloseHandle, INVALID_HANDLE_VALUE> CAutoFile;
The client code looks like this:
CAutoLibrary hLibrary = LoadLibrary(_T("psapi.dll"));
So, from the clients point of view,
CSmartHandle looks like
EnsureCleanup. The last thing to do is to write the
CSmartHandle class itself.
Class CSmartHandle
The
CSmartHandle template class has the following three parameters:
HandleType - HANDLE, HKEY or other handle type.
ReleaseAlgorithm - this parameter is a template itself. It defines the Release Policy.
NULL_VALUE - non-type parameter which defines the incorrect handle value - NULL or INVALID_HANDLE_VALUE.
CSmartHandle class is derived from the Release Policy. Its header looks like this:
template <typename HandleType,
template <typename> class ReleaseAlgorithm,
HandleType NULL_VALUE = NULL>
class CSmartHandle : public ReleaseAlgorithm<HandleType>
{
....
};The first and third parameters are simple: handle type and null value with default parameter
NULL. The second parameter is a template template parameter. Writing
template <typename> class ReleaseAlgorithm we define the second parameter as a template which has one class/typename parameter. This is a type definition like a function prototype:
void SimeFunction(int); ReleaseAlgorithm template is instantiated with the
HandleType type in the
CSmartHandle code.
Note: class and typename keywords are equivalent in the template definition. According to [2] typename is better to use for template parameters which can be both simple types and classes.
The full
CSmartHandle code:
template <typename HandleType,
template <typename> class ReleaseAlgorithm,
HandleType NULL_VALUE = NULL>
class CSmartHandle : public ReleaseAlgorithm<HandleType>
{
public:
CSmartHandle()
{
m_Handle = NULL_VALUE;
}
CSmartHandle(HandleType h)
{
m_Handle = h;
}
HandleType operator= (HandleType h)
{
CleanUp();
m_Handle = h;
return(*this);
}
operator HandleType()
{
return m_Handle;
}
BOOL IsValid()
{
return m_Handle != NULL_VALUE;
}
~CSmartHandle()
{
CleanUp();
}
protected:
void CleanUp()
{
if ( m_Handle != NULL_VALUE )
{
Close(m_Handle); m_Handle = NULL_VALUE;
}
}
HandleType m_Handle;
};
Extending class functionality
As template class clients we can extend its functionality by providing our own Release Policies. What about adding a Release Policy which just deletes plain pointers? With such a Release Policy the
CSmartHandle class works like a very primitive smart pointer. However, first we need to add
operator-> to the class:
HandleType operator->()
{
return m_Handle;
} The Release Policy looks like:
template <typename T>
struct CClosePointer
{
void Close(T handle)
{
delete handle;
}
};It's impossible to make a
typedef for the client code because
typedef has no parameters. Client could easily use
CSmartHandle with
CClosePointer passing the pointer type as a parameter. Additional template
CAutoPointer helps to do this:
template <class T>
struct CAutoPointer
{
typedef CSmartHandle<T*, CClosePointer> AutoPtr;
};Client code:
CAutoPointer<CMyClass>::AutoPtr pMyClass = new CMyClass();
pMyClass->DoSomething();
The Demo project HandleTest also contains a
CCloseArrayPointer policy for use with array pointers.
Misuse of template class
Adding the smart pointer functionality creates some problems. Consider the following code:
CAutoLibrary hLibrary;
hLibrary->unused = 0;
This code is compiled and effectively crashes the program at run time. What happens? The handles are defined by such way:
struct HINSTANCE__ { int unused; }; typedef struct HINSTANCE__ *HINSTANCE;All handles are
void* pointers. However, SDK headers define each handle as a pointer to the structure with some informative name, which has the dummy member
unused. The following code is compiled as well:
HINSTANCE h;
h->unused = 0;
As a template writers, we should always prefer to detect template misuse at compile time and not at run time. This problem may be solved by adding an additional parameter to the
CSmartHandle template. This parameter is
class PointedBy, or pointee. It is defined as empty class for the handles, which prevents using an instance of the class as a pointer. With such change the
CSmartHandle definition looks more exciting:
template <typename HandleType,
template <class, class> class ReleaseAlgorithm,
class PointedBy = CEmptyClass,
HandleType NULL_VALUE = NULL>
class CAutoHandle : public ReleaseAlgorithm<HandleType, PointedBy>
{
....
};See details in the second Demo project, HandleTest2.
Performance issues
Consider the following two code fragments, first using
CSmartHandle:
int _tmain(int argc, _TCHAR* argv[])
{
FARPROC address;
CAutoLibrary hLibrary = LoadLibrary(_T("psapi.dll"));
if ( hLibrary.IsValid() )
{
address = GetProcAddress(hLibrary, _T("EnumProcesses"));
}
return 0;
}and second without
CSmartHandle:
int _tmain(int argc, _TCHAR* argv[])
{
FARPROC address;
HINSTANCE hLibrary = LoadLibrary(_T("psapi.dll"));
if ( hLibrary )
{
address = GetProcAddress(hLibrary, _T("EnumProcesses"));
FreeLibrary(hLibrary);
}
return 0;
}In the Release configuration both code fragments produce exactly the same Assembly code:
push esi
push offset string "psapi.dll" (4050FCh)
call dword ptr [__imp__LoadLibraryA@4 (405008h)]
mov esi,eax
test esi,esi
je main+25h (401025h) push offset string "EnumProcesses" (4050ECh)
push esi
call dword ptr [__imp__GetProcAddress@8 (405004h)]
push esi
call dword ptr [__imp__FreeLibrary@4 (405000h)]
xor eax,eax
pop esi
We can see that the C++ compiler is smart enough to optimize this code so that there is no overhead at all when we use CSmartHandle template. This is what are templates about - make computer work hard at compile time, having optimized well-performing code at run time.
Conclusion
The
CSmartHandle class presented in this article is not intended for practical use. It's smart pointer capabilities are restricted, and it works like
EnsureCleanup as a handle wrapper. The purpose of this article is to show some of new features which are available for Visual C++ programmers with Visual Studio 2003 Release. For example, the Loki library introduced by Andrei Alexandrescu in
[2] is now compilable in Visual C++ (I got hundreds of compilation errors in previous Visual C++ versions). Visual Studio .NET 2002 was developed mostly for VB, WEB and database programmers. Visual Studio .NET 2003 is for C++ programmers.
Sources
- Programming Server-Side Applications for Microsoft® Windows® 2000. Jeffrey Richter, Jason D. Clark. Microsoft Press.
- Modern C++ Design. Andrei Alexandrescu. ADDISON-WESLEY.