12.11 C++11新增的這個智能指針特別霸道,只允許自己訪問相關內存

本文將結合C++11標準中的智能指針std::unique_ptr<>類的簡單使用實例,討論其基本原理,以期快速瞭解該智能指針類的使用。

C++11新增的這個智能指針特別霸道,只允許自己訪問相關內存

std::unique_ptr<> 是什麼?

std::unique_ptr<> 是什麼?

std::unique_ptr<>是C++語言中提供的一種智能指針類,使用它可以方便的管理指針,儘可能的避免內存洩漏。unique_ptr 對象可以用於維護普通(常常用於索引一塊內存)的指針,在其生命週期結束後,自動地刪除它,並釋放相關的內存。unique_ptr 重載了->和*運算符,因此可以像使用普通指針那樣使用 unique_ptr 智能指針。下面是一段簡單的C++語言代碼示例,請看:

#include <iostream>
#include <memory>

struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout< }
~Task()
{
std::cout< }
};

int main()
{
// 創建一個 unique_ptr 對象,並且綁定到 new Task(23) 上
std::unique_ptr<task> taskPtr(new Task(23));
// 通過智能指針訪問成員變量
int id = taskPtr->mId;
std::cout<
return 0;
}
/<task>/<memory>/<iostream>
C++11新增的這個智能指針特別霸道,只允許自己訪問相關內存

C++代碼示例

這段C++語言代碼很簡單,main() 函數首先創建了一個 unique_ptr 智能指針對象,new Task(23)本來需要一個Task *指針索引,現在直接使用 unique_ptr 對象 taskPtr 管理它,所以代碼中沒有使用delete刪除相應的指針,因為“智能”指針對象 taskPtr 會在相關指針生命週期結束後自動地刪除它。編譯這段C++語言代碼時,記得指定-std=c++11選項,最終得到的輸出如下:

# g++ t1.cpp -std=c++11
# ./a.out
Task::Constructor
23
Task::Destructor

事實上,不管函數是正常還是不正常(程序拋出異常等)的退出,taskPtr 的析構函數總是會被調用的,因此,taskPtr 管理的 raw 指針會被自動刪除,避免內存洩漏。

unique_ptr 的“專享所有權”

unique_ptr 中的“unique”一詞有著“唯一的,獨一無二”的意思,這主要體現在所有權上,某個 raw 指針同時只能被一個 unique_ptr 指針綁定,我們不能拷貝 unique_ptr 對象,只能轉移。事實上,鑑於 unique_ptr 對象相對於其管理的 raw 指針的獨一無二特性,其內部不需要像shared_ptr<>智能指針類那樣需要“引用計數”機制,一旦 unique_ptr 對象的析構函數被調用,它就會刪除掉綁定的 raw 指針。

C++11新增的這個智能指針特別霸道,只允許自己訪問相關內存

使用 unique_ptr<> 智能指針類

創建一個空 unique_ptr 對象

std::unique_ptr<> 本質上是一個在標準命名空間std中的模板類,使用它需要包含頭文件<memory>,例如定義一個可以綁定 int 指針的對象的C++語言代碼可以如下寫:/<memory>

#include <memory>

std::unique_ptr ptr;/<memory>

還可以在定義 unique_ptr 對象的時候傳入需要與之綁定的“raw指針”,這一點在前面的C++語言代碼實例中已經見過:

std::unique_ptr<task> taskPtr(new Task(23));/<task>

需要注意,不能直接把 raw 指針直接賦值給 unique_ptr 對象,也就是說下面這行C++語言代碼是非法的:

taskPtr = new Task(23); // 非法

檢查 unique_ptr 對象是否為空

上面定義的 unique_ptr 對象 ptr 沒有與任何 raw 指針綁定,因此它是空的。檢查 unique_ptr 對象是否為空,一般有兩種方法,相關的C++語言代碼示例如下,請看:

  • 方法 1:
if(!ptr)
std::cout<
  • 方法 2:
if(ptr == nullptr)
std::cout<

重置(reset)unique_ptr

調用 unique_ptr 的 reset() 方法將刪除與之綁定的 raw 指針,並且將 unique_ptr 對象置空:

taskPtr.reset();
// taskPtr==nullptr 為真

上面這行C++語言代碼執行後,與 taskPtr 綁定的 raw 指針將會被刪除,並且 taskPtr 被置空,也即taskPtr==nullptr為真。

unique_ptr 對象不可拷貝

鑑於 unique_ptr 不可拷貝,只能移動,所以我們不能通過拷貝構造函數活著賦值操作拷貝 unique_ptr 對象,下面這兩行C++語言代碼都是非法的:

std::unique_ptr<task> taskPtr2 = taskPtr; // 非法
taskPtr = taskPtr2; // 非法/<task>

轉移 unique_ptr 對象的所有權

如前文所述,我們不能拷貝 unique_ptr 對象,卻可以移動它,所謂“移動”,其實就是轉移所有權,請看下面這個示例,首先創建一個 unique_ptr 對象:

std::unique_ptr<task> taskPtr2(new Task(55));/<task>

此時 taskPtr2 顯然不為空。現在將其對綁定 raw 指針的所有權轉移到一個新的 unique_ptr 對象,相關的C++語言代碼可以如下寫:

std::unique_ptr<task> taskPtr4 = std::move(taskPtr2);

if(taskPtr2 == nullptr)
std::cout<
// taskPtr2 對 raw 指針的所有權被轉移給 taskPtr4 了
if(taskPtr4 != nullptr)
std::cout<

std::move() 函數將 taskPtr2 轉換為右值(rvalue)引用,所以 unique_ptr 的移動構造函數可以將與 taskPtr2 綁定的 raw 指針轉移給 taskPtr4,在這之後,taskPtr2 變為空。

釋放對綁定 raw 指針的所有權

unique_ptr 的 release() 函數可以直接將對綁定 raw 指針的所有權釋放,該函數會將綁定的 raw 指針返回,請看下面的C++語言代碼示例:

std::unique_ptr<task> taskPtr5(new Task(55));

if(taskPtr5 != nullptr)
std::cout<
// 釋放所有權
Task * ptr = taskPtr5.release();

if(taskPtr5 == nullptr)
std::cout<

執行完上面的代碼後,taskPtr5 變為空,並且其綁定的 raw 指針被賦值給 ptr。

完整示例

下面以一段完整的C++語言代碼示例結束本文:

#include <iostream>
#include <memory>

struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout< }
~Task()
{
std::cout< }
};

int main()
{
std::unique_ptr ptr1;
if(!ptr1)

std::cout< if(ptr1 == nullptr)
std::cout<
// 不能直接將 raw 指針賦值給 unique_ptr 對象
// std::unique_ptr<task> taskPtr2 = new Task(); // 非法

std::unique_ptr<task> taskPtr(new Task(23));
if(taskPtr != nullptr)
std::cout< // 通過 unique_ptr 直接訪問成員變量
std::cout<< taskPtr->mId <<:endl>
taskPtr.reset();
std::cout< if(taskPtr == nullptr)
std::cout<
std::unique_ptr<task> taskPtr2(new Task(55));
// unique_ptr 不可賦值,不可拷貝
//taskPtr = taskPtr2; // 非法
//std::unique_ptr<task> taskPtr3 = taskPtr2; // 非法

{
// 轉移所有權
std::unique_ptr<task> taskPtr4 = std::move(taskPtr2);
if(taskPtr2 == nullptr)
std::cout< if(taskPtr4 != nullptr)
std::cout< // taskPtr4 作用域尾部,生命週期結束,
// 將刪除 taskPtr2 轉移過來的 raw 指針
}
std::unique_ptr<task> taskPtr5(new Task(55));
// 釋放所有權
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout< std::cout<<ptr->mId<<:endl> // 此時需要手動刪除用完的指針
delete ptr;

return 0;
}/<ptr->/<task>/<task>/<task>/<task>/<task>/<task>
/<memory>/<iostream>
C++11新增的這個智能指針特別霸道,只允許自己訪問相關內存

代碼看著不方便的話,可以點擊文章末尾的“瞭解更多”

同樣的,編譯時需要指定-std=c++11,最終輸出如下,請看:

# g++ t2.cpp -std=c++11
# ./a.out
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is not empty
23
Task::Destructor
Reset the taskPtr
taskPtr is empty
Task::Constructor
taskPtr2 is empty
taskPtr4 is not empty
Task::Destructor
Task::Constructor
taskPtr5 is empty
55
Task::Destructor

小結

本文主要討論了C++11標準中的智能指針 unique_ptr 類的基本使用和一些相關注意事項。不過應該明白,文中的示例使用的deleter是 unique_ptr 的默認 deleter,也即delete方法,這樣的侷限性實際上很大,原因和解決方法可以參考:shared_ptr自定義deleter 。

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

代碼看著不方便的話,可以點擊文章末尾的“瞭解更多”。

"/<task>
/<task>


分享到:


相關文章: