Click here to Skip to main content
Click here to Skip to main content

Writing a Smart Handle class using template template parameters

, 9 Jun 2003
Rate this:
Please Sign up or sign in to vote.
An article describing the use of template template parameters

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"));
// No need to release library handle - it is closed in class destructor
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);         // call to the policy function
            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

  1. Programming Server-Side Applications for Microsoft® Windows® 2000. Jeffrey Richter, Jason D. Clark. Microsoft Press.
  2. Modern C++ Design. Andrei Alexandrescu. ADDISON-WESLEY.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Alex Fr
Software Developer
Israel Israel
No Biography provided

Comments and Discussions

 
GeneralNice work! Pinmemberhgrund16-Jun-03 19:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 10 Jun 2003
Article Copyright 2003 by Alex Fr
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid