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()
{
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
{
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);
}
};
#define safe_access_start(Ref) if (Ref.is_valid()) {
#define safe_access_end }
#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.
{
Stest *sTest = new Stest(10);
sa::ToRef<Stest, bool, bool> testRef(sTest, false);
safe_access_start(testRef)
testRef.r_get().access();
safe_access_end
}
{
Stest *sTest1 = new Stest(20);
sa::ToRef<Stest, bool, bool> testRef(sTest1, true);
safe_access_start(testRef)
testRef.r_get().access();
safe_access_end
}
Stest *sTest_1 = NULL;
{
sa::ToRef<Stest, bool, bool> testRef(sTest_1, false);
safe_access_start(testRef)
testRef.r_get().access();
safe_access_end
fast_access_start
testRef.r_get().access();
fast_access_end
}
{
sa::ToRef<Stest, bool, bool> testRef(sTest_1, true);
safe_access_start(testRef)
testRef.r_get().access();
safe_access_end
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.
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()
{
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
{
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
{
sa::ToNRef<Stest, bool, bool> testRef(sTest_1, true);
safe_access_start(testRef)
testRef.r_get().access();
safe_access_end
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.