65.9K
CodeProject 正在变化。 阅读更多。
Home

安全地访问指针

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.71/5 (6投票s)

2013年1月8日

CPOL

2分钟阅读

viewsIcon

25859

downloadIcon

209

本文讨论了如何安全地访问指针,如果使用不当,指针就像不定时炸弹。

引言

在 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。

© . All rights reserved.