|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionMicrosoft 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 WARNING: this code is compiled only on Visual Studio .NET 2003. It uses features unsupported in previous Visual Studio versions. Class DesignLet's decide what the class
Each Release Policy class is a template class with a Handle Type parameter. The 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 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 destructorSo, from the clients point of view, CSmartHandle looks like EnsureCleanup. The last thing to do is to write the CSmartHandle class itself.
Class CSmartHandleTheCSmartHandle template class has the following three parameters:
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: 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 functionalityAs 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 theCSmartHandle 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 classAdding 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 issuesConsider the following two code fragments, first usingCSmartHandle: 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 ConclusionTheCSmartHandle 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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||