智能指針:
智能指針的好處就是, 當智能指針過期時, 其析構函數将使用delete來釋放記憶體. 是以, 如果将new傳回的位址指派給這些對象, 将無需記住稍後釋放這些記憶體: 在隻能指針過期時, 這些記憶體将自動被釋放.
有三個隻能指針模闆: auto_ptr, unique_ptr 和 shared_ptr 都定義了類似指針的對象, 可以将new獲得(直接或間接)的位址賦給這種對象.
要建立隻能指針對象, 必須包含頭檔案memory, 然後使用通常的模闆文法來執行個體化所需類型的指針, 例如:
template<class X> class auto_ptr {
public:
explicit auto_ptr(X * p = 0) throw();
...
}
// pd是一個指向double的auto_ptr
auto_ptr<double> pd(new double);
// ps是一個執行string的auto_ptr, 相當于string * ps;
auto_ptr<string> ps(new string);
其他兩種智能指針的用法相同:
unique_ptr<double> pdu(new double);
shared_ptr<string> pss(new string);
智能指針模闆位于名稱空間std中. 下面我們來看一個完整的例子:
// smartptrs.cpp
#include <iostream>
#include <string>
#include <memory>
class Report
{
private:
std::string str;
public:
Report(const std::string s): str(s) {
std::cout << "Object created!" << std::endl;
}
~Report() {
std::cout << "Object deleted!\n";
}
void comment() const {std::cout << str << "\n"; }
};
int main()
{
{
std::auto_ptr<Report> ps(new Report("using auto_ptr"));
ps->comment();
}
{
std::shared_ptr<Report> ps(new Report("using shared_ptr"));
ps->comment();
}
{
std::unique_ptr<Report> ps(new Report("using unique_ptr"));
ps->comment();
}
return 0;
}
程式運作結果為:
所有智能指針類都有一個explicit構造函數, 該構造函數将指針作為參數. 是以不需要自動将指針轉換為智能指針對象:
shared_ptr<double> pd;
double * p_reg = new double;
// 這個是非法的轉換, 因為p_reg是指針, 而pd是隻能指針對象
pd = p_reg;
// 合法的
pd = shared_ptr<double>(p_reg);
// 非法的
shared_ptr<double> pshared = p_reg;
// 合法的
shared_ptr<double> pshared(p_rege=);
需要注意的一點: 對全部三種智能指針都應避免的一點:
string vacation("I wandered lonely as a cloud");
shared_ptr<string> pvac(&vacation);
pvac過期時, 程式将把delete運算符用于非堆記憶體, 這是錯誤的.
有關智能指針的注意事項:
先看下面的指派語句:
auto_ptr<string> ps(new string("I reigned lonely as a cloud"));
auto_ptr<string> vocation;
vocation = ps;
如果ps和vocation是正常指針, 則兩個指針将指向同一個string對象, 這是不能接受的, 因為程式将試圖删除同一個對象兩次--一次是ps過期時, 另一次是vocation過期時, 為避免這種問題, 有以下幾種方法:
1. 定義指派運算符, 使之執行深複制. 這樣兩個指針将指向不同的對象, 其中一個對象是另一個對象的副本.
2. 建立所有權(ownership)概念, 對于特定的對象, 隻能有一個隻能指針可擁有它, 這樣隻有擁有對象的隻能指針的構造函數會删除該對象, 然後, 讓指派操作轉讓所有權. 這就是auto_ptr和unique_ptr的政策, 但unique_ptr的政策更嚴格.
3. 建立智能更高的指針, 跟蹤引用特定對象的智能指針數, 稱之為引用計數. 例如: 指派時, 計數将加1, 而指針過期時, 計數将減1. 僅當最後一個指針過期時, 才調用delete. 這是 shared_ptr 采用的政策.
來看一個完整的例子:
// fowl.cpp, 智能指針的例子
#include <iostream>
#include <string>
#include <memory>
int main()
{
using namespace std;
auto_ptr<string> films[5] = {
auto_ptr<string> (new string("First word")),
auto_ptr<string> (new string("Second word")),
auto_ptr<string> (new string("Third word")),
auto_ptr<string> (new string("Fourth word")),
auto_ptr<string> (new string("Fifth word"))
};
auto_ptr<string> pwin;
pwin = films[2];
cout << "The nominees for best avian baseball film are \n";
for(int i = 0; i < 5; i++)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << endl;
cin.get();
return 0;
}
程式運作結果為:
請看下面的語句:
auto_ptr<string> p1(new string("auto"));
auto_ptr<string> p2;
p2 = p1;
在第三行代碼中, p2接管string對象的所有權後, p1的所有權将被剝奪. 但如果程式随後試圖使用p1, 這件更引起異常, 因為p1不再指向有效的資料. 這也正是上面的fowl.cpp運作出錯的原因.
如果我們換成unique_ptr, 會有不同的效果:
unique_ptr<string> p3(new string("auto"));
unique_ptr<string> p4;
p4 = p3;
編譯器會認為上面第三行非法, 這樣就避免了p3不再指向有效資料的問題. 是以unique_ptr比 auto_ptr更安全.
但是注意一點: 程式試圖将一個unique_ptr賦給另一個時, 如果原unique_ptr是一個臨時右值, 則編譯器允許這樣使用, 但如果原unique_ptr将存在一段時間, 編譯器将禁止這樣做. 例如:
using namespace std;
unique_ptr<string> pu1(new string("Hello"));
unique_ptr<string> pu2;
// 非法的
pu2 = pu1;
unique_ptr<string> pu3;
// 合法的.
pu3 = unique_ptr<string>(new string("world"));
如果我們一定要使用上述的pu2 = pu1; 這種操作. 僅當以非智能的方式使用遺棄的智能指針(如接觸引用時), 這種指派才不安全. 要安全地重用這種指針, 可以給它賦新值. C++提供了一個标準庫函數std::move(), 讓我們能夠講一個unique_ptr賦給另一個. 來看一個例子:
using namespace std;
unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp (new string(s));
return temp;
}
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1);
ps1 = demo(" and more");
cout << *ps2 << *ps1 << endl;
相比于auto_ptr, unique_ptr還有另一個優點. 它有一個用于數組的變體. 注意, 必須将delete和new配對使用, 将delete[]和new[]配對使用. 模版auto_ptr使用delete而不是delete[], 是以隻能與new一起使用, 而不能與new[]一起使用. 但unique_ptr有使用new[]和delete[]的版本:
std::unique_ptr<double[]> pda(new double[5]);
如何選擇智能指針:
1. 如果程式要使用多個指向同一個對象的指針, 應選擇shared_ptr.
2. 如果程式不需要多個指向同一個對象的指針, 則可以使用unique_ptr. 如果函數使用new配置設定記憶體, 并傳回指向該記憶體的指針, 推薦使用unique_ptr.
3. 在unique_ptr為右值時, 可将其賦給shared_ptr.