unique_ptr 是 C++ 11 提供的用於防止內存泄漏的智能指針中的一種實現,獨享被管理對象指針所有權的智能指針。unique_ptr對象包裝一個原始指針,並負責其生命週期。當該對象被銷燬時,會在其析構函數中刪除關聯的原始指針。
unique_ptr具有->
和*
運算符重載符,因此它可以像普通指針一樣使用。
查看下面的示例:
#include <iostream>
#include <memory>
struct Task {
int mId;
Task(int id ) :mId(id) {
std::cout << "Task::Constructor" << std::endl;
}
~Task() {
std::cout << "Task::Destructor" << std::endl;
}
};
int main()
{
// 通過原始指針創建 unique_ptr 實例
std::unique_ptr<Task> taskPtr(new Task(23));
//通過 unique_ptr 訪問其成員
int id = taskPtr->mId;
std::cout << id << std::endl;
return 0;
}
輸出:
Task::Constructor
23
Task::Destructor
unique_ptr <Task>
對象 taskPtr
接受原始指針作爲參數。現在當main函數退出時,該對象超出作用範圍就會調用其析構函數,在unique_ptr
對象taskPtr
的析構函數中,會刪除關聯的原始指針,這樣就不用專門delete Task對象了。
這樣不管函數正常退出還是異常退出(由於某些異常),也會始終調用taskPtr的析構函數。因此,原始指針將始終被刪除並防止內存泄漏。
unique_ptr 獨享所有權
unique_ptr對象始終是關聯的原始指針的唯一所有者。我們無法複製unique_ptr對象,它只能移動。
由於每個unique_ptr對象都是原始指針的唯一所有者,因此在其析構函數中它直接刪除關聯的指針,不需要任何參考計數。
創建一個空的 unique_ptr 對象
創建一個空的unique_ptr<int>
對象,因爲沒有與之關聯的原始指針,所以它是空的。
std::unique_ptr<int> ptr1;
檢查 unique_ptr 對象是否爲空
有兩種方法可以檢查 unique_ptr 對象是否爲空或者是否有與之關聯的原始指針。
// 方法1
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
使用原始指針創建 unique_ptr 對象
要創建非空的 unique_ptr 對象,需要在創建對象時在其構造函數中傳遞原始指針,即:
std::unique_ptr<Task> taskPtr(new Task(22));
不能通過賦值的方法創建對象,下面的這句是錯誤的
// std::unique_ptr<Task> taskPtr2 = new Task(); // 編譯錯誤
使用 std::make_unique 創建 unique_ptr 對象 / C++14
std::make_unique<>() 是C++ 14 引入的新函數
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);
獲取被管理對象的指針
使用get()
·函數獲取管理對象的指針。
Task *p1 = taskPtr.get();
重置 unique_ptr 對象
在 unique_ptr 對象上調用reset()
函數將重置它,即它將釋放delete關聯的原始指針並使unique_ptr 對象爲空。
taskPtr.reset();
unique_ptr 對象不可複製
由於 unique_ptr 不可複製,只能移動。因此,我們無法通過複製構造函數或賦值運算符創建unique_ptr對象的副本。
// 編譯錯誤 : unique_ptr 不能複製
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// 編譯錯誤 : unique_ptr 不能複製
taskPtr = taskPtr2; //compile error
轉移 unique_ptr 對象的所有權
我們無法複製 unique_ptr 對象,但我們可以轉移它們。這意味着 unique_ptr 對象可以將關聯的原始指針的所有權轉移到另一個 unique_ptr 對象。讓我們通過一個例子來理解:
// 通過原始指針創建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 把taskPtr2中關聯指針的所有權轉移給taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 現在taskPtr2關聯的指針爲空
if(taskPtr2 == nullptr)
std::cout<<"taskPtr2 is empty"<<std::endl;
// taskPtr2關聯指針的所有權現在轉移到了taskPtr4中
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
// 會輸出55
std::cout<< taskPtr4->mId << std::endl;
std::move()
將把 taskPtr2
轉換爲一個右值引用。因此,調用 unique_ptr 的移動構造函數,並將關聯的原始指針傳輸到 taskPtr4
。在轉移完原始指針的所有權後, taskPtr2
將變爲空。
釋放關聯的原始指針
在 unique_ptr 對象上調用 release()
將釋放其關聯的原始指針的所有權,並返回原始指針。這裏是釋放所有權,並沒有delete原始指針,reset()
會delete原始指針。
std::unique_ptr<Task> taskPtr5(new Task(55));
// 不爲空
if(taskPtr5 != nullptr)
std::cout<<"taskPtr5 is not empty"<<std::endl;
// 釋放關聯指針的所有權
Task * ptr = taskPtr5.release();
// 現在爲空
if(taskPtr5 == nullptr)
std::cout<<"taskPtr5 is empty"<<std::endl;
完整示例程序
#include <iostream>
#include <memory>
struct Task {
int mId;
Task(int id ) :mId(id) {
std::cout<<"Task::Constructor"<<std::endl;
}
~Task() {
std::cout<<"Task::Destructor"<<std::endl;
}
};
int main()
{
// 空對象 unique_ptr
std::unique_ptr<int> ptr1;
// 檢查 ptr1 是否爲空
if(!ptr1)
std::cout<<"ptr1 is empty"<<std::endl;
// 檢查 ptr1 是否爲空
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
// 不能通過賦值初始化unique_ptr
// std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error
// 通過原始指針創建 unique_ptr
std::unique_ptr<Task> taskPtr(new Task(23));
// 檢查 taskPtr 是否爲空
if(taskPtr != nullptr)
std::cout<<"taskPtr is not empty"<<std::endl;
// 訪問 unique_ptr關聯指針的成員
std::cout<<taskPtr->mId<<std::endl;
std::cout<<"Reset the taskPtr"<<std::endl;
// 重置 unique_ptr 爲空,將刪除關聯的原始指針
taskPtr.reset();
// 檢查是否爲空 / 檢查有沒有關聯的原始指針
if(taskPtr == nullptr)
std::cout<<"taskPtr is empty"<<std::endl;
// 通過原始指針創建 unique_ptr
std::unique_ptr<Task> taskPtr2(new Task(55));
if(taskPtr2 != nullptr)
std::cout<<"taskPtr2 is not empty"<<std::endl;
// unique_ptr 對象不能複製
//taskPtr = taskPtr2; //compile error
//std::unique_ptr<Task> taskPtr3 = taskPtr2;
{
// 轉移所有權(把unique_ptr中的指針轉移到另一個unique_ptr中)
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 轉移後爲空
if(taskPtr2 == nullptr)
std::cout << "taskPtr2 is empty" << std::endl;
// 轉進來後非空
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
std::cout << taskPtr4->mId << std::endl;
//taskPtr4 超出下面這個括號的作用於將delete其關聯的指針
}
std::unique_ptr<Task> taskPtr5(new Task(66));
if(taskPtr5 != nullptr)
std::cout << "taskPtr5 is not empty" << std::endl;
// 釋放所有權
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout << "taskPtr5 is empty" << std::endl;
std::cout << ptr->mId << std::endl;
delete ptr;
return 0;
}
輸出:
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is empty
Task::Constructor
taskPtr2 is not empty
taskPtr2 is empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor
總結
new出來的對象是位於堆內存上的,必須調用delete才能釋放其內存。
unique_ptr 是一個裝指針的容器,且擁有關聯指針的唯一所有權,作爲普通變量使用時系統分配對象到棧內存上,超出作用域時會自動析構,unique_ptr對象的析構函數中會delete其關聯指針,這樣就相當於替我們執行了delete堆內存上的對象。
成員函數 | 作用 |
---|---|
reset() | 重置unique_ptr爲空,delete其關聯的指針。 |
release() | 不delete關聯指針,並返回關聯指針。釋放關聯指針的所有權,unique_ptr爲空。 |
get() | 僅僅返回關聯指針 |
unique_ptr不能直接複製,必須使用std::move()
轉移其管理的指針,轉移後原 unique_ptr 爲空。std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
創建unique_ptr對象有兩種方法:
//C++11:
std::unique_ptr<Task> taskPtr(new Task(23));
//C++14:
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);