Click here to Skip to main content
15,896,063 members
Articles / Programming Languages / C++

A Guard class for any object/resource type

Rate me:
Please Sign up or sign in to vote.
4.76/5 (7 votes)
15 Apr 2007CPOL9 min read 38.5K   124   18  
An elegant way to wrap all types that need a cleanup, to minimize the chances of memory/resource leaks.
#pragma once

// The core template class for guarded operations.
// Incapsulates a wrapper around some type/object that needs cleanup.
template <class T>
class GBase_T {
protected:
	T m_Object;
	void DoDestroy()
	{
		if (IsValid())
			m_Object.Destroy();
	}
	void DoSetNull()
	{
		m_Object.m_Value = T::GetNullValue();
	}
public:
	// Destructor
	~GBase_T()
	{
		DoDestroy();
	}
	// Standard operations
	void Destroy()
	{
		if (IsValid())
		{
			m_Object.Destroy();
			DoSetNull();
		}
	}
	typename T::GuardType Detach()
	{
		typename T::GuardType val = m_Object.m_Value;
		DoSetNull();
		return val;
	}
	void Attach(typename T::GuardType val)
	{
		DoDestroy();
		m_Object.m_Value = val;
	}
	// Extractors
	bool IsValid() const
	{
		return T::GetNullValue() != m_Object.m_Value;
	}
	bool IsNull() const
	{
		return T::GetNullValue() == m_Object.m_Value;
	}
	operator typename T::GuardType() const
	{
		return m_Object.m_Value;
	}
	operator bool () const
	{
		return IsValid();
	}
	// A more convenient way to access an object for some types
	typename T::GuardType operator ->()
	{
		return m_Object.m_Value;
	}
	typename T::GuardType GetValue()
	{
		return m_Object.m_Value;
	}
	// The following method should be used with care!
	typename T::GuardType& GetRef()
	{
		return m_Object.m_Value;
	}
};

// A bit specialized version. Incapsulates objects that don't support referencing, only destruction.
// For such objects it is illegal to either assign one wrapper to another or use copy constructors,
// since in such a case two wrappers will attempt to destroy the object.
template <class T>
class GObj_T :public GBase_T<T> {
	// Disable copy constructor and assignment.
	GObj_T(const GObj_T& val);
	GObj_T& operator = (const GObj_T& val);
public:
	// Constructors
	GObj_T()
	{
		DoSetNull();
	}
	GObj_T(typename T::GuardType val)
	{
		m_Object.m_Value = val;
	}
	// Assignment
	GObj_T& operator = (typename T::GuardType val)
	{
		DoDestroy();
		m_Object.m_Value = val;
		return *this;
	}
};

// The following specialized version wraps objects that support referencing/dereferencing. Unlike
// previous case, here we can enable both copy constructors and all forms of the assignment.
template <class T>
class GRef_T :public GBase_T<T> {
protected:
	void DoAssign(typename T::GuardType val)
	{
		m_Object.m_Value = val;
		if (IsValid())
			m_Object.Reference();
	}
public:
	// Constructors
	GRef_T()
	{
		DoSetNull();
	}
	GRef_T(typename T::GuardType val)
	{
		DoAssign(val);
	}
	GRef_T(const GRef_T& val)
	{
		DoAssign(val);
	}
	GRef_T(typename T::GuardType val, bool bAddRef)
	{
		if (bAddRef)
			DoAssign(val);
		else
			m_Object.m_Value = val;
	}
	// Assignments
	GRef_T& operator = (typename T::GuardType val)
	{
		// This is needed if you intend to assign an object to itself.
		if (val != m_Object.m_Value)
		{
			DoDestroy();
			DoAssign(val);
		}
		return *this;
	}
	GRef_T& operator = (const GRef_T& val)
	{
		return operator = (val.m_Object.m_Value);
	}
};

// In order to use those guard tempate classes we've just defined - usually it's enough to
// typedef GObj_T/GRef_T<something>, but in some cases we'll also want to override those classes
// for some extra-functionality.
// Alas, the C++ compiler is stupid enough to generate copy constructor and assignment
// operator automatically, by just copying the mem. Of course this can't be a correct
// behaviour in our case. Because of this we've implemented those two functions explicitly
// in GObj_T/GRef_T classes.
// But unfortunately this is not enough to be absolutely sure the compiler won't ruine that.
// If you override a class and forget to re-write copy constructor/assignment the compiler
// will generate those two functions automatically again, incorrectly of course.
// To avoid such cases we obey the following rule: NEVER inherit the GObj_T class directly.
// Use the following macros instead:

#define INHERIT_GUARD_OBJ_BASE(gnew, gbase, gtype) \
private: \
	gnew(const gnew&); \
	gnew(const gbase&); \
	gnew& operator = (const gnew&); \
	gnew& operator = (const gbase&); \
public: \
	gnew& operator = (gtype val) { gbase::operator = (val); return *this; }

#define INHERIT_GUARD_OBJ(gnew, gbase, gtype) \
	INHERIT_GUARD_OBJ_BASE(gnew, gbase, gtype) \
	gnew() {} \
	gnew(gtype val) :gbase(val) {}

#define INHERIT_GUARD_REF_BASE(gnew, gbase, gtype) \
public: \
	gnew(const gnew& val) :gbase(val) {} \
	gnew(const gbase& val) :gbase(val) {} \
	gnew& operator = (gtype val) { gbase::operator = (val); return *this; } \
	gnew& operator = (const gnew& val) { gbase::operator = (val); return *this; } \
	gnew& operator = (const gbase& val) { gbase::operator = (val); return *this; }

#define INHERIT_GUARD_REF(gnew, gbase, gtype) \
	INHERIT_GUARD_REF_BASE(gnew, gbase, gtype) \
	gnew() {} \
	gnew(gtype val) :gbase(val) {} \
	gnew(gtype val, bool bAddRef) :gbase(val, bAddRef) {}

// Helper template classes, that are to be used in conjugtion with GObj_T/GRef_T classes.
// Core, carries the 'object' being guarded.
template <class T>
struct GBaseH_Core {
	T m_Value;
	typedef T GuardType;
};
// Implements GetNullValue by returning NULL of specified type, applicable for most of the cases.
template <class T>
struct GBaseH_Null {
	static T GetNullValue() { return NULL; }
};
// Implements GetNullValue by returning -1, applicable for some common types, such as files, sockets, and etc.
template <class T>
struct GBaseH_Minus1 {
	static T GetNullValue() { return (T) -1; }
};
// Mixes
template <class T>
struct GBaseH_CoreNull :public GBaseH_Core<T>, public GBaseH_Null<T> {
};
template <class T>
struct GBaseH_CoreMinus1 :public GBaseH_Core<T>, public GBaseH_Minus1<T> {
};

// Pointers to objects allocated via new operator.
template <class T>
struct GBaseH_Ptr :public GBaseH_CoreNull<T*> {
	void Destroy() { delete m_Value; }
};
template <class T>
class GPtr_T :public GObj_T<GBaseH_Ptr<T> >
{
	INHERIT_GUARD_OBJ(GPtr_T, GObj_T<GBaseH_Ptr<T> >, T*)
};


////////////////////////////////////////////////////////////
// Specialized tempate classes, for specific guard types.

// Incapsulates Win32 handles, that must be closed with CloseHandle
struct GBaseH_HandleClose :public GBaseH_Core<HANDLE> {
	void Destroy() { VERIFY(CloseHandle(m_Value)); }
};
// Set of standard Win32 handles
struct GBaseH_HandleCloseStd :public GBaseH_HandleClose, public GBaseH_Null<HANDLE> {
};
// Set of Win32 file handles
struct GBaseH_HandleCloseFile :public GBaseH_HandleClose, public GBaseH_Minus1<HANDLE> {
};
// Applicable for FindFirstFile
struct GBaseH_HandleFindClose :public GBaseH_Core<HANDLE>, public GBaseH_Minus1<HANDLE> {
	void Destroy() { VERIFY(FindClose(m_Value)); }
};

typedef GObj_T<GBaseH_HandleCloseFile>	HFile_G;
typedef GObj_T<GBaseH_HandleFindClose>	HFileFind_G;
typedef GObj_T<GBaseH_HandleCloseStd>	Handle_G;

// HGlobal
struct GBaseH_HGlobal : public GBaseH_CoreNull<HGLOBAL> {
	void Destroy() { VERIFY(!GlobalFree(m_Value)); }
};
typedef GObj_T<GBaseH_HGlobal>		HGlobal_G;

// GDI objects.
struct GBaseH_GdiObj : public GBaseH_CoreNull<HGDIOBJ> {
	void Destroy() { VERIFY(DeleteObject(m_Value)); }
};
typedef GObj_T<GBaseH_GdiObj>			HGdiObj_G;

// Sockets
struct GBaseH_Socket : public GBaseH_CoreMinus1<SOCKET> {
	void Destroy() { VERIFY(!closesocket(m_Value)); }
};
typedef GObj_T<GBaseH_Socket>			Socket_G;

// HCrypt objects.
struct GBaseH_CryptHash : public GBaseH_CoreNull<HCRYPTHASH> {
	void Destroy() { VERIFY(CryptDestroyHash(m_Value)); }
};
struct GBaseH_CryptKey : public GBaseH_CoreNull<HCRYPTKEY> {
	void Destroy() { VERIFY(CryptDestroyKey(m_Value)); }
};
typedef GObj_T<GBaseH_CryptHash>		HCryptHash_G;
typedef GObj_T<GBaseH_CryptKey>			HCryptKey_G;

// File mapping
struct GBaseH_UnmapFile : public GBaseH_CoreNull<PBYTE> {
	void Destroy() { VERIFY(UnmapViewOfFile(m_Value)); }
};
typedef GObj_T<GBaseH_UnmapFile>		HFileMapping_G;

// Module loaded via LoadLibrary
struct GBaseH_Module : public GBaseH_CoreNull<HMODULE> {
	void Destroy() { VERIFY(FreeLibrary(m_Value)); }
};
typedef GObj_T<GBaseH_Module>			HModule_G;

// Some types of HDCs
typedef GBaseH_CoreNull<HDC> GBaseH_Hdc;

struct GBaseH_HdcDelete :public GBaseH_Hdc {
	void Destroy() { VERIFY(DeleteDC(m_Value)); }
};
typedef GObj_T<GBaseH_HdcDelete>		HDC_G;

struct GBaseH_HdcRelease :public GBaseH_Hdc {
	HWND m_hWnd; // extra member added.
	void Destroy() { ReleaseDC(m_hWnd, m_Value); }
};
class HDC_Release_G :public GObj_T<GBaseH_HdcRelease> {
	INHERIT_GUARD_OBJ_BASE(HDC_Release_G, GObj_T<GBaseH_HdcRelease>, HDC)
	HDC_Release_G(HWND hWnd)
	{
		m_Object.m_hWnd = hWnd;
	}
};

struct GBaseH_HdcEndPaint :public GBaseH_Hdc {
	HWND m_hWnd;
	PAINTSTRUCT m_PS;
	void Destroy() { VERIFY(EndPaint(m_hWnd, &m_PS)); }
};
class HDC_Paint_G :public GObj_T<GBaseH_HdcEndPaint> {
	INHERIT_GUARD_OBJ_BASE(HDC_Paint_G, GObj_T<GBaseH_HdcEndPaint>, HDC)
	HDC_Paint_G(HWND hWnd)
	{
		m_Object.m_hWnd = hWnd;
	}
	HDC Begin()
	{
		DoDestroy();
		return BeginPaint(m_Object.m_hWnd, &m_Object.m_PS);
	}
};

// Selecting an HGDIOBJ into an HDC.
struct GBaseH_HdcSelect : public GBaseH_CoreNull<HGDIOBJ> {
	HDC m_hDC;
	void Destroy() { VERIFY(SelectObject(m_hDC, m_Value)); }
};

class HDC_Select_G : public GObj_T<GBaseH_HdcSelect>
{
	INHERIT_GUARD_OBJ_BASE(HDC_Select_G, GObj_T<GBaseH_HdcSelect>, HGDIOBJ)
	HDC_Select_G(HDC hDC)
	{
		m_Object.m_hDC = hDC;
	}
};

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer (Senior)
Israel Israel
My name is Vladislav Gelfer, I was born in Kiev (former Soviet Union), since 1993 I live in Israel.
In programming I'm interested mostly in low-level, OOP design, DSP and multimedia.
Besides of the programming I like physics, math, digital photography.

Comments and Discussions