Safely accessing pointers






3.71/5 (6 votes)
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
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.
//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.
//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
//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.