首页 > 代码库 > 解引用NULL指针

解引用NULL指针

一般导致程序崩溃的最重要原因之一就是试图解引用NULL指针。正如上几篇文章中所说的,智能指针RefCountPtr和ScopedPtr提供了运行时的诊断。但是,并不是所有的指针都是拥有某个对象所有的智能指针。因此为了对试图解引用一个不具有对象所有权的指针的行为进行诊断,引入一种并不删除它所指向的对象的“半智能”指针。例如,如下代码示例:

template <typename T>
class Ptr
{
	public:
		explicit Ptr(T* p = NULL)
			: ptr_(p)
		{
		}

		T* Get() const
		{
			return ptr_;
		}

		Ptr<T>& operator=(T* p)
		{
			ptr_ = p;
			return *this;
		}

		T* operator->() const
		{
			SCPP_TEST_ASSERT(ptr_ != NULL,
				"Attempt to use operator -> on NULL pointer.");
			return ptr_;
		}

		T& operator*() const
		{
			SCPP_TEST_ASSERT(ptr_ != NULL,
				"Attempt to use operator * on NULL pointer.");
			return *ptr_;
		}

	private:
		T* ptr_;

};

尽管出现了操作符=,但它并不是告诉编译器当我们试图把一个Ptr<T>赋值给另一个Ptr<T>时该怎么做的赋值符。如果我们为这个类编写一个赋值操作符,它应该被声明为如下这种形式:

Ptr<T>& operator=(const Ptr<T>& that);

注意在前面这个类中,操作符=具有不同的签名:它的右边有一个原始指针p。因此,这个类让编译器为Ptr<T>创建拷贝构造函数和复制操作符。由于Ptr<T>类的拷贝构造函数和赋值操作符都是允许出现的,因此,我们可以自由复制这些指针,或者把它们作为函数的返回值等

另外一种情况,如果建议我们用Ptr<T>代替T*,对于const T*指针该使用什么?答案为:Ptr<const T>。假设如下的这个类:

class MyClass
{
public:
	explicit MyClass(int id)
		: id_(id)
	{
	}

	int GetId() const
	{
		return id_;
	}

	void SetId(int id)
	{
		id_ = id;
	}

private:
	int id_;
};

如果想创建一个行为与const MyClass*相似的半智能指针,只能像下面的做法一样:

scpp::Ptr<const MyClass> p(new MyClass(1));
cout<<"Id = "<<p->GetId()<<endl;  //能够编译并运行
p->SetId(666); //无法通过编译

注意,试图通过这个指针调用一个非常量函数将将无法通过编译,这意味着它正确的表现了常量指针的行为。

对于Ptr<T>模板指针具有以下特性:

(1)它并不拥有它所指向的对象的所有权,应该作为相同情况下原始指针的替代品;

(2)它默认被初始化为NULL;

(3)它提供了运行时诊断,当它本身为NULL时,如果对它进行调用,就可以对这种行为进行检测。

小结:

  • 如果指针拥有它所指向的对象的所有权,就使用智能指针
  • 如果是不拥有所指向的对象的所有权的原始指针T*,就用模板类Ptr<T>取而代之
  • 对于常量指针(cosnt T*),使用Ptr<cosnt T>