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) // to xor eax, eax
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.