一個(gè) unique_ptr “擁有” 它所指向的對(duì)象。與 shared_ptr 不同,某個(gè)時(shí)刻只能有一個(gè) unique_ptr 指向一個(gè)給定對(duì)象,當(dāng) unique_ptr 被銷毀時(shí),它所指向的對(duì)象也被銷毀。
與 shared_ptr 不同,沒有類似 make_shared 的標(biāo)準(zhǔn)庫(kù)函數(shù)返回一個(gè) unique_ptr,當(dāng)我們定義一個(gè) unique_ptr 時(shí),需要將其綁定到一個(gè) new 返回的指針上。類似 shared_ptr,初始化 unique_ptr 必須采用直接初始化形式:
unique_ptr<double> p1; // 可以指向一個(gè) double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一個(gè)值為 42 的 int
由于一個(gè) unique_ptr 擁有它指向的對(duì)象,因此 unique_ptr 不支持普通的拷貝或賦值操作:
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // 錯(cuò)誤:unique_ptr 不支持拷貝
unique_ptr<string> p3;
p3=p2; // 錯(cuò)誤:unique_ptr不支持賦值
shared_ptr 和 unique_ptr 都支持的操作
| 操作 | 說明 |
|---|---|
| shared_ptr<T> sp unique_ptr<T> up |
空智能指針,可以指向類型為 T 的對(duì)象 |
| p | 將 p 用作一個(gè)條件判斷,若 p 指向一個(gè)對(duì)象,則為 true |
| *p | 解引用 p,獲得它指向的對(duì)象 |
| p->mem | 等價(jià)于(*p).mem |
| p.get() | 返回 p 中保存的指針。要小心使用,若智能指針釋放了其對(duì)象,返回的指針?biāo)赶虻膶?duì)象也就消失了 |
| swap(p, q) p.swap(q) |
交換 p 和 q 中的指針 |
只適用于 unique_ptr 的操作
| 操作 | 說明 |
|---|---|
| unique_ptr<T> u1 | 空 unique_ptr,可以指向類型為 T 的對(duì)象 u1 會(huì)使用 delete 來釋放它的指針 |
| unique_ptr<T, D> u2 | 空 unique_ptr,可以指向類型為 T 的對(duì)象 u2 會(huì)使用一個(gè)類型為 D 的可調(diào)用對(duì)象來釋放它的指針 |
| unique_ptr<T, D> u(d) | 空 unique_ptr,指向類型為 T 的對(duì)象,用類型為 D 的對(duì)象 d 代替 de1ete |
| u = nullptr | 釋放 u 指向的對(duì)象,將 u 置為空 |
| u.release() | u 放棄對(duì)指針的控制權(quán),返回指針,并將 u 置為空 |
| u.reset() | 釋放 u 指向的對(duì)象 |
| u.reset(q) u.reset(nullptr) |
如果提供了內(nèi)置指針 q,令 u 指向這個(gè)對(duì)象;否則將 u 置為空 |
雖然我們不能拷貝或賦值 unique_ptr,但可以通過調(diào)用 release 或 reset 將指針的所有權(quán)從一個(gè)(非 const)unique_ptr 轉(zhuǎn)移給另一個(gè) unique:
// 將所有權(quán)從 p1(指向 string Stegosaurus)轉(zhuǎn)移給 p2
unique_ptr<string> p2(p1.release()); // release 將 p1 置為空
unique_ptr<string> p3(new string("Trex"));
// 將所有權(quán)從 p3 轉(zhuǎn)移給 p2
p2.reset(p3, release()); // reset 釋放了 p2 原來指向的內(nèi)存
release 成員返回 unique_ptr 當(dāng)前保存的指針并將其置為空。因此,p2 被初始化為 p1 原來保存的指針,而 p1 被置為空。
reset 成員接受一個(gè)可選的指針參數(shù),令 unique_ptr 重新指向給定的指針,如果 unique_ptr 不為空,它原來指向的對(duì)象被釋放。因此,對(duì) p2 調(diào)用 reset 釋放了用 “Stegosaurus” 初始化的 string 所使用的內(nèi)存,將 p3 對(duì)指針的所有權(quán)轉(zhuǎn)移給 p2,并將 p3 置為空。
調(diào)用 release 會(huì)切斷 unique_ptr 和它原來管理的對(duì)象間的聯(lián)系。release 返回的指針通常被用來初始化另一個(gè)智能指針或給另一個(gè)智能指針賦值。在本例中,管理內(nèi)存的責(zé)任簡(jiǎn)單地從一個(gè)智能指針轉(zhuǎn)移給另一個(gè),但是,如果我們不用另一個(gè)智能指針來保存 release 返回的指針,我們的程序就要負(fù)責(zé)資源的釋放:
p2.release(); // 錯(cuò)誤:p2 不會(huì)釋放內(nèi)存,而且我們丟失了指針
auto p = p2.release(); // 正確,但我們必須記得 delete(p)
傳遞 unique_ptr 參數(shù)和返回 unique_ptr
不能拷貝 unique_ptr 的規(guī)則有一個(gè)例外:我們可以拷貝或賦值一個(gè)將要被銷毀的 unique_ptr,最常見的例子是從函數(shù)返回一個(gè) unique_ptr:
unique_ptr<int> clone(int p) {
// 正確:從 int* 創(chuàng)建一個(gè) unique_ptr<int>
return unique_ptr<int>(new int(p));
}
還可以返回一個(gè)局部對(duì)象的拷貝:
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int(p));
// ...
return ret;
}
對(duì)于兩段代碼,編譯器都知道要返回的對(duì)象將要被銷毀。
向后兼容:auto_ptr
標(biāo)準(zhǔn)庫(kù)的較早版本包含了一個(gè)名為 auto_ptr 的類,它具有unique_ptr 的部分特性,但不是全部。特別是,我們不能在容器中保存 auto_ptr,也不能從函數(shù)中返回 auto_ptr。雖然 auto_ptr 仍是標(biāo)準(zhǔn)庫(kù)的一部分,但編寫程序時(shí)應(yīng)該使用 unique_ptr。
向 unique_ptr 傳遞刪除器
類似 shared_ptr,unique_ptr 默認(rèn)情況下用 delete 釋放它指向的對(duì)象,與 shared_ptr 一樣,我們可以重載一個(gè) unique_ptr 中默認(rèn)的刪除器。但是,unique_ptr 管理刪除器的方式與 shared_ptr 不同。
重載一個(gè) unique_ptr 中的刪除器會(huì)影響到 unique_ptr 類型以及如何構(gòu)造(或 reset)該類型的對(duì)象。與重載關(guān)聯(lián)容器的比較操作類似我們必須在尖括號(hào)中 unique_ptr 指向類型之后提供刪除器類型。在創(chuàng)建或 reset 一個(gè)這種 unique_ptr 類型的對(duì)象時(shí),必須提供一個(gè)指定類型的可調(diào)用對(duì)象(刪除器):
// p 指向一個(gè)類型為 objT 的對(duì)象,開使用一個(gè)類型為 delT 的對(duì)象釋放 objT 對(duì)象
// 它會(huì)調(diào)用一個(gè)名為 fcn 的 delT 類型對(duì)象
unique_ptr<objT, delT> p(new objT, fcn);
作為一個(gè)更具體的例子,我們將重寫連接程序,用 unique_ptr 米代替 shared_ptr 如下所示:
void f(destination &d /* 其他需要的參數(shù) */) {
connection c = connect(&d); // 打開連接
// 當(dāng) p 被銷毀時(shí),連接將會(huì)關(guān)閉
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection):
// 使用連接
// 當(dāng) f 退出時(shí)(即使是由于異常而退出),connection 會(huì)被正確關(guān)閉
}
在本例中我們使用了 decltype 來指明函數(shù)指針類型。由于 decltype(end_connection)返回一個(gè)函數(shù)類型,所以我們必須添加一個(gè) * 來指出我們正在使用該類型的一個(gè)指針。
unique_ptr 和動(dòng)態(tài)數(shù)組
標(biāo)準(zhǔn)庫(kù)提供了一個(gè)可以管理 new 分配的數(shù)組的 unique_ptr 版本。為了用一個(gè) unique_ptr 管理動(dòng)態(tài)數(shù)組,我們必須在對(duì)象類型后面跟一對(duì)空方括號(hào):
// up 指向一個(gè)包含 10 個(gè)未初始化 int 的數(shù)組
unique_ptr<int[]> up(new int[10]);
up.release(); // 自動(dòng)用 delete[] 銷毀其指針
類型說明符中的方括號(hào)(<int[]>)指出 up 指向一個(gè) int 數(shù)組而不是一個(gè) int。由于 up 指向一個(gè)數(shù)組,當(dāng) up 銷毀它管理的指針時(shí),會(huì)自動(dòng)使用 delete[]。
指向數(shù)組的 unique_ptr 提供的操作與我們?cè)谥惺褂玫哪切┎僮饔幸恍┎煌?。?dāng)一個(gè) unique_ptr 指向一個(gè)數(shù)組時(shí),我們不能使用點(diǎn)和箭頭成員運(yùn)算符。畢竟 unique_ptr 指向的是一個(gè)數(shù)組而不是單個(gè)對(duì)象,因此這些運(yùn)算符是無(wú)意義的。另一方面,當(dāng)一個(gè) unique_ptr 指向一個(gè)數(shù)組時(shí)我們可以使用下標(biāo)運(yùn)算符來訪問數(shù)組中的元素:
for (size_t i = 0; i != 10; ++i) {
up[i] = i; // 為每個(gè)元素賦予一個(gè)新值
}
| 指向數(shù)組的 unique_ptr | 說明 |
|---|---|
| 指向數(shù)組的 unique_ptr 不支持成員訪問運(yùn)算符(點(diǎn)和箭頭運(yùn)算符) | 其他 unique_ptr 操作不變 |
| unique_ptr<T[]> u | u 可以指向一個(gè)動(dòng)態(tài)分配的數(shù)組,數(shù)組元素類型為 T |
| unique_ptr<T[]> u(p) | u 指向內(nèi)置指針 p 所指向的動(dòng)態(tài)分配的數(shù)組。p 必須能轉(zhuǎn)換為類型 T* |
| u[i] | u 必須指向一個(gè)數(shù)組,返回 u 擁有的數(shù)組中位置 i 處的對(duì)象 |