Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC10.0

Safely accessing pointers

3.71/5 (6 votes)
27 Jan 2013CPOL2 min read 25.4K   193  
The topic discusses how to safely access the pointers, which are considered as bomb shells if used unsafely.

Introduction

Accessing a pointer can be problematic in C++ and it requires a lot of good practices to ensure it correct usage. The article discusses how to safely access the pointers using one of the common practice.

Background

Generally pointers are accessed by using the dereference operator. It however can lead to bizarre results if the memory location is not accessible any more.

Using the code

The following piece of code can be used to correctly deal with unsafe pointers. I used templates to create a class ToRef, which need to be instantiated for the type of check on the pointers required, and delete policy (whether it should be deleted after usage).

The code is fairly simple and it simply checks for the pointers before returning the object instead of pointers to use it safely.

I would recommend using r_get() to access the pointer's object and p_get is provided only for tracing and inheritance purposes.

ToRef class

C++
template <typename T_TYPE, typename T_RET, typename T_DELETE_POLICY>
class ToRef: public std::tr1::tuple<T_TYPE*const&, T_RET, T_DELETE_POLICY>
{
public:
    ToRef<T_TYPE, T_RET, T_DELETE_POLICY>(T_TYPE*const& p_type, const T_DELETE_POLICY delete_policy)
        : std::tr1::tuple<T_TYPE*const&, T_RET, T_DELETE_POLICY>(p_type, p_type!=NULL, delete_policy)
    {
    }
};

template <typename T_TYPE>
class ToRef<T_TYPE, bool, bool>: public std::tr1::tuple<T_TYPE*const&, bool, bool>
{
public:
    explicit ToRef<T_TYPE, bool, bool>(T_TYPE*const& p_type, const bool delete_policy)
        :std::tr1::tuple<T_TYPE*const&, bool, bool>(p_type, p_type!=NULL, delete_policy)
    {
    }

    ~ToRef()
    {
        //delete policy

        if (std::tr1::get<2>(*this))
        {
            if (NULL != std::tr1::get<0>(*this))
                delete std::tr1::get<0>(*this);

            const_cast<T_TYPE*&>(std::tr1::get<0>(*this))= NULL;
        }
    }

private:
    ToRef<T_TYPE, bool, bool>(ToRef<T_TYPE, bool, bool>& copy){};
    ToRef<T_TYPE, bool, bool>& operator = (ToRef<T_TYPE, bool, bool>& rhs){};

public:
    bool is_valid(void) const
    {
        //validity of the pointer.
        return std::tr1::get<1>(*this);
    }
    T_TYPE& r_get(void) const
    {
        if (is_valid())
            return *std::tr1::get<0>(*this);
        throw std::string("Invalid Pointer");
    }

    T_TYPE*const & p_get(void) const
    {
        return std::tr1::get<0>(*this);
    }
};

//use it to safely access the pointers.
//if block is an overhead here.
#define safe_access_start(Ref) if (Ref.is_valid()) {
#define safe_access_end }

//faster mode but unsafe. exception handling takes care of preventing
//unhandled exception to cascade to the top.
#define fast_access_start try {
#define fast_access_end } catch (std::string s_exception){ TRACE("%s, %d, %s", __FILE__, __LINE__, s_exception.c_str());}

The pointer validity check can be changed based on the policy of checking the pointers. In that case one need to change the ToRef instantiation and defines the is_valid appropriately.

Using the ToRef template class

The usage is quite simple too. One can use the macros safe_access_start() and safe_access_end to access the pointers. This however has some overhead of checking the pointers everytime one need to access it. The other alternative is to use fast_access_start, fast_access_end, which handles the exception if there is an invalid pointers.

C++
//first test. access only, no delete of memory
	{
		Stest *sTest = new Stest(10);
		//don't delete the memory when done.
		sa::ToRef<Stest, bool, bool> testRef(sTest, false);
	
		//this will safely access the .access() function
		safe_access_start(testRef)
			testRef.r_get().access();
		safe_access_end
	}


	//second test, delete associate memory
	{
		Stest *sTest1 = new Stest(20);
		//delete memory when done.
		sa::ToRef<Stest, bool, bool> testRef(sTest1, true);
		
		//this will safely access the .access() function
		safe_access_start(testRef)
			testRef.r_get().access();
		safe_access_end
	}

	// null pointers. 
	Stest *sTest_1 = NULL;
	{
		sa::ToRef<Stest, bool, bool> testRef(sTest_1, false);
		
		//this will by pass the .access() call
		safe_access_start(testRef)
			testRef.r_get().access();
		safe_access_end
		
		//this will throw an exception
		fast_access_start
			testRef.r_get().access();
		fast_access_end

	}

	//null pointers with delete policy
	{
		sa::ToRef<Stest, bool, bool> testRef(sTest_1, true);
		
		//this will by pass the .access() call
		safe_access_start(testRef)
			testRef.r_get().access();
		safe_access_end
		
		//this will throw an exception
		fast_access_start
			testRef.r_get().access();
		fast_access_end
	}

Using ToNRef template class

The ToNRef template class solves another problem, where one would like to initialize a pointer with default constructor if the pointer is invalid. This can also be combined with valid check and delete policy.

C++
//use in case you want to initialize a pointer if it is null
template <typename T_TYPE, typename T_RET, typename T_DELETE_POLICY>
class ToNRef: public std::tr1::tuple<T_TYPE*&, T_RET, T_DELETE_POLICY>
{
public:
    ToNRef<T_TYPE, T_RET, T_DELETE_POLICY>(T_TYPE*& p_type, const T_DELETE_POLICY delete_policy)
        : std::tr1::tuple<T_TYPE*&, T_RET, T_DELETE_POLICY>(p_type, (p_type== NULL? p_type= new T_TYPE(1):p_type)!=NULL, delete_policy)
    {
    }
};

template <typename T_TYPE>
class ToNRef<T_TYPE, bool , bool>: public std::tr1::tuple<T_TYPE*&, bool, bool>
{

public:
    ToNRef<T_TYPE, bool, bool>(T_TYPE*& p_type, const bool delete_policy)
        : std::tr1::tuple<T_TYPE*&, bool, bool>(p_type, (p_type== NULL? p_type= new T_TYPE(1):p_type)!=NULL, delete_policy)
    {
    }

    ~ToNRef()
    {
        //delete policy
        if (std::tr1::get<2>(*this))
        {
            if (NULL != std::tr1::get<0>(*this))
                delete std::tr1::get<0>(*this);

            const_cast<T_TYPE*&>(std::tr1::get<0>(*this))= NULL;
        }
    }

private:
    ToNRef<T_TYPE, bool, bool>(ToNRef<T_TYPE, bool, bool>& copy){};
    ToNRef<T_TYPE, bool, bool>& operator = (ToNRef<T_TYPE, bool, bool>& rhs){};

public:
    bool is_valid(void) const
    {
        //validity of the pointer.
        return std::tr1::get<1>(*this);
    }

    T_TYPE& r_get(void) const
    {
        if (is_valid())
            return *std::tr1::get<0>(*this);
        throw std::string("Invalid Pointer");
    }

    T_TYPE*const & p_get(void) const
    {
        return std::tr1::get<0>();
    }
};

Using the ToNRef class

C++
//null pointers with reinitialization.
	{
		sa::ToNRef<Stest, bool, bool> testRef(sTest_1, true);
		
		////this will by pass the .access() call
		safe_access_start(testRef)
			testRef.r_get().access();
		safe_access_end
		
		//this will throw an exception
		fast_access_start
			testRef.r_get().access();
		fast_access_end
	}

Restrictions

It however has some shortcomings. The code is not thread-safe so one would have to explicitly takes care of atomicity if the code is intended for use in multi-threading environment. The code with deletion policy is not meant to be used with the shared pointers.

Copying of the ToRef and ToNRef is prevented by making copy constructor and assignment operator private.

License

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