一般導致程序崩潰的最重要原因之一就是試圖解引用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>