安全地访问指针






3.71/5 (6投票s)
本文讨论了如何安全地访问指针,如果使用不当,指针就像不定时炸弹。
引言
在 C++ 中访问指针可能存在问题,需要遵循许多良好的实践来确保其正确使用。本文讨论了如何使用一种常见的实践安全地访问指针。
背景
通常使用解引用运算符来访问指针。但是,如果内存位置不再可访问,这可能会导致奇怪的结果。
使用代码
以下代码可用于正确处理不安全的指针。我使用了模板来创建一个类 ToRef,需要为所需的指针检查类型和删除策略(是否在使用后应删除它)实例化该类。
这段代码非常简单,它只是在返回对象之前检查指针的有效性,而不是返回指针本身,从而安全地使用它。
我建议使用 r_get() 来访问指针的对象,而 p_get() 仅用于跟踪和继承目的。
ToRef 类
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());}
指针有效性检查可以根据检查指针的策略进行更改。在这种情况下,需要更改 ToRef 的实例化,并相应地定义 is_valid。
使用 ToRef 模板类
使用起来也很简单。可以使用宏 safe_access_start() 和 safe_access_end() 来访问指针。但是,这会带来每次需要访问指针时都进行检查的开销。另一种选择是使用 fast_access_start、fast_access_end,它会在指针无效时处理异常。
//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
}
使用 ToNRef 模板类
ToNRef 模板类解决了另一个问题,即如果指针无效,则希望使用默认构造函数初始化指针。这也可以与有效性检查和删除策略结合使用。
//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>();
}
};
使用 ToNRef 类
//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
}
限制
但是,它有一些缺点。该代码不是线程安全的,因此如果代码要在多线程环境中使用,则必须显式地考虑原子性。带有删除策略的代码不应用于共享指针。
通过将复制构造函数和赋值运算符设为私有来防止复制 ToRef 和 ToNRef。