1. 概述
本想將unique_ptr, shared_ptr和weak_ptr寫在同一篇文章中,無奈越(廢)寫(話)越(連)長(篇),本著不給自己和讀者太大壓力的原則,最終決定分為三篇去描述它們(不是惡意湊文章數哦),
本篇文章主要描述了unique_ptr,在此之前先給出了auto_ptr的介紹,廢話不說,直入正題,
2. auto_ptr
auto_ptr是在C++ 98中引入的,在C++ 17中被移除掉,它的引入是為了管理動態分配的記憶體,它的移除是因為本身有嚴重的缺陷,并且已經有了很好的替代者(unique_ptr),
auto_ptr采用"Copy"語意,期望實作"Move"語意,有諸多的問題,標準庫中的auto_ptr和《Move語意和Smart Pointers先導(以一個例子說明)》中的AutoPtr2十分類似,此處再次給出代碼并分析它的問題,
template<typename T>
struct AutoPtr2
{
AutoPtr2(T* ptr = nullptr)
: ptr(ptr)
{
}
~AutoPtr2()
{
if(this->ptr != nullptr)
{
delete this->ptr;
this->ptr = nullptr;
}
}
AutoPtr2(AutoPtr2& ptr2) // not const
{
this->ptr = ptr2.ptr;
ptr2.ptr = nullptr;
}
AutoPtr2& operator=(AutoPtr2& ptr2) // not const
{
if(this == &ptr2)
{
return *this;
}
delete this->ptr;
this->ptr = ptr2.ptr;
ptr2.ptr = nullptr;
return *this;
}
T& operator*() const
{
return *this->ptr;
}
T* operator->() const
{
return this->ptr;
}
bool isNull() const
{
return this->ptr == nullptr;
}
private:
T* ptr;
};
以上采用"Copy"語意,期望實作"Move"語意的實作有以下三大問題:
- auto_ptr采用拷貝構造和拷貝賦值構造去實作"Move"語意,若將auto_ptr采用值傳遞作為函式的引數,當函式執行結束時會導致資源被釋放,若之后的代碼再次訪問此auto_ptr則會是nullptr;
- 由于auto_ptr總是使用"non-array delete",所以它不能用于管理array類的動態記憶體;
- auto_ptr不能和STL容器和演算法配合作業,因為STL中的"Copy"真的是"Copy",而不是"Move",
由于auto_ptr有諸多問題,需要一個更加完美的"Smart Point",unique_ptr也就應運而生了,
3. unqiue_ptr
3.1 Smart Points簡介
Smart Points是什么,或者說它是用來干什么的?它是用來管理動態分配的記憶體的,它能夠動態地分配資源且能夠在適當的時候釋放掉曾經動態分配的記憶體,
此時對智能指標來說就有兩條原則:
- 智能指標本身不能是動態分配的,否則它自身有不被釋放的風險,進而可能導致它所管理物件不能正確地被釋放;
- 在堆疊上分配智能指標,讓它指向堆上動態分配的物件,這樣就能保證智能指標所管理的物件能夠合理地被釋放,
3.2 unique_ptr的實作
unique_ptr是獨占式的,即完全擁有它所管理物件的所有權,不和其它的物件共享,標準庫中的實作和《Move constructors 和 Move assignment constructors簡介》中的AutoPtr4十分相似,代碼如下:
template<typename T>
struct AutoPtr4
{
AutoPtr4(T* ptr = nullptr)
: ptr(ptr)
{
}
~AutoPtr4()
{
if(this->ptr != nullptr)
{
delete this->ptr;
this->ptr = nullptr;
}
}
AutoPtr4(const AutoPtr4& ptr4) = delete; // disable copying
AutoPtr4(AutoPtr4&& ptr4) noexcept // move constructor
: ptr(ptr4)
{
ptr4.ptr = nullptr;
}
AutoPtr4& operator=(const AutoPtr4& ptr4) = delete; // disable copy assignment
AutoPtr4& operator=(AutoPtr4&& ptr4) noexcept // move assignment
{
if(this == &ptr4)
{
return *this;
}
delete this->ptr;
this->ptr = ptr4.ptr;
ptr4.ptr = nullptr;
return *this;
}
T& operator*() const
{
return *this->ptr;
}
T* operator->() const
{
return this->ptr;
}
bool isNull() const
{
return this->ptr == nullptr;
}
private:
T* ptr;
};
從中可以看到,unique_ptr禁用了拷貝構造和拷貝賦值構造,僅僅實作了移動構造和移動賦值構造,這也就使得它是獨占式的,
3.3 unique_ptr的使用
3.3.1 unique_ptr的基本使用
下面是一個unique_ptr的例子,此處的res是在堆疊上的區域變數,在main()結束時會被銷毀,它管理的資源也會被釋放掉,
#include <iostream>
#include <memory> // for std::unique_ptr
struct Resource
{
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::unique_ptr
std::unique_ptr<Resource> res{ new Resource() };
return 0;
} // the allocated Resource is destroyed here
以下的代碼講解unique_ptr和"Move"語意:
#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
struct Resource
{
Resource()
{
std::cout << "Resource acquired" << std::endl;
}
~Resource()
{
std::cout << "Resource destroyed" << std::endl;
}
};
int main()
{
std::unique_ptr<Resource> res1{ new Resource{} };
std::unique_ptr<Resource> res2{};
std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null" : "null") << std::endl;
std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null" : "null") << std::endl;
// res2 = res1; // Won't compile: copy assignment is disabled
res2 = std::move(res1); // res2 assumes ownership, res1 is set to null
std::cout << "Ownership transferred" << std::endl;
std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null" : "null") << std::endl;
std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null" : "null") << std::endl;
return 0;
} // Resource destroyed here
以上代碼的運行結果如下:
Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed
由于unique_ptr禁止了"Copy"語意,所以"res2 = res1;"不能編譯通過,如果我們想轉移unique_ptr管理的一個物件的所有權怎么辦?可以采用"Move"語意,即通過move()將res1轉化為一個右值,此時再將它賦值給res2就會呼叫移動賦值建構式實作所有權的轉移,
3.3.2 訪問管理的物件
unique_ptr有多載的"operator*"和"operator->",即它和普通的指標具有相似的訪問物件的方法,
其中"operator*"回傳它管理物件的參考,"operator->"回傳一個指向它管理物件的指標,
3.3.3 unique_ptr和array
不同于auto_ptr只能有"delete",unique_ptr可以有"delete"和"array delete",其中,unique_ptr對于std::array, std::vector和std::string的支持比較友好,
3.3.4 make_unique
std::make_unique是C++ 14才引入的(詳見參考文獻3,此處不詳細展開),它能夠創建并回傳 unique_ptr 至指定型別的物件,它完美傳遞了引數給物件的建構式,從一個原始指標構造出一個std::unique_ptr,回傳創建的std::unique_ptr,其大概的實作如下:
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
此處需要記住優選std::make_unique(),而不是自己去創建一個std::unique_ptr,
3.3.5 unique_ptr作為函式的回傳值
unique_ptr可以作為函式的回傳值,如下的代碼:
struct Resource
{
...
};
std::unique_ptr<Resource> createResource()
{
return std::make_unique<Resource>();
}
int main()
{
auto ptr{ createResource() };
...
return 0;
}
可以看到unique_ptr作為值在createResource()函式中回傳,并在main()函式中通過"Move"語意將所有權轉移給ptr,
3.3.6 unique_ptr作為函式引數傳遞
若要函式接管指標的所有權,可以通過值傳遞unique_ptr,且要采用"Move"語意,
#include <iostream>
#include <memory>
#include <utility>
struct Resource
{
Resource()
{
std::cout << "Resource acquired" << std::endl;
}
~Resource()
{
std::cout << "Resource destroyed" << std::endl;
}
friend std::ostream& operator<<(std::ostream& out, const Resource& res)
{
out << "I am a resource";
return out;
}
};
void takeOwnership(std::unique_ptr<Resource> res)
{
if (res)
{
std::cout << *res << std::endl;
}
} // the Resource is destroyed here
int main()
{
auto ptr{ std::make_unique<Resource>() };
takeOwnership(std::move(ptr)); // move semantics
std::cout << "Ending program" << std::endl;
return 0;
}
以上的代碼輸出如下:
Resource acquired
I am a resource
Resource destroyed
Ending program
從中可以看到,所有權被函式takeOwnership()接管,當函式執行完畢時資源即被釋放,
然而大多數時候我們只是想通過函式呼叫去改變智能指標管理的物件,而不是讓函式接管所有權,此時我們可以通過傳遞原始的指標或者參考來實作,如下:
#include <iostream>
#include <memory>
struct Resource
{
Resource()
{
std::cout << "Resource acquired" << std::endl;
}
~Resource()
{
std::cout << "Resource destroyed" << std::endl;
}
friend std::ostream& operator<<(std::ostream& out, const Resource& res)
{
out << "I am a resource";
return out;
}
};
void useResource(const Resource* res)
{
if (res)
{
std::cout << *res << std::endl;
}
}
int main()
{
auto ptr{ std::make_unique<Resource>() };
useResource(ptr.get()); // get(): get a pointer to the Resource
std::cout << "Ending program" << std::endl;
return 0;
} // The Resource is destroyed here
以上代碼的輸出如下:
Resource acquired
I am a resource
Ending program
Resource destroyed
3.3.7 unique_ptr作為類的成員變數
unique_ptr還可以作為類的成員變數,以下代碼中的普通指標怎么用unique_ptr替換?詳見參考文獻4,
普通指標版本:
struct Device
{
...
};
struct Settings
{
Settings(Device* device)
{
this->device = device;
}
Device* getDevice()
{
return device;
}
private:
Device* device;
};
int main()
{
Device* device = new Device();
Settings settings(device);
...
Device* myDevice = settings.getDevice();
...
delete device;
}
unique_ptr版本:
#include <memory>
struct Device
{
...
};
struct Settings
{
Settings(std::unique_ptr<Device> d)
{
device = std::move(d);
}
Device& getDevice()
{
return *device;
}
private:
std::unique_ptr<Device> device;
};
int main()
{
std::unique_ptr<Device> device(new Device());
Settings settings(std::move(device));
...
Device& myDevice = settings.getDevice();
...
}
3.3.8 其它用法
unique_ptr的其它用法如下:

3.3.9 unique_ptr的誤用
常見的誤用有兩種:
- 多個智能指標物件管理同一個資源:
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
std::unique_ptr<Resource> res2{ res };
unique_ptr是獨占的,另外res1和res2的生命周期結束后都會釋放同一塊資源,從而導致未定義的錯誤,
- unique_ptr管理資源后,又自定義了delete資源:
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
delete res;
在res1的生命周期結束時會去釋放已經被delete釋放過的資源,從而導致未定義的錯誤,
4. 總結
本文通過對auto_ptr的介紹引出了unique_ptr,總結了unique_ptr的實作以及一些常用的方法,并給出了常見的錯誤使用,
5. 參考文獻
- Move語意和Smart Pointers先導(以一個例子說明),https://www.jianshu.com/p/0c9b4e1e7b9f
- Move constructors 和 Move assignment constructors簡介,https://www.jianshu.com/p/f97e211fdc2d
- c++ 之智能指標:盡量使用std::make_unique和std::make_shared而不直接使用new,https://blog.csdn.net/p942005405/article/details/84635673
- C++智能指標作為成員變數,https://www.jianshu.com/p/3402d90a5647
歡迎大家批評指正、評論和轉載(請注明源出處),謝謝!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/295929.html
標籤:C++
