一. make系列函數
(一)三個make函數
1. std::make_shared:用于建立shared_ptr。GCC編譯器中,其内部是通過調用std::allocate_shared來實作的。
2. std::make_unique:C++14中加入标準庫。
3. std::allocate_shared:行為和std::make_shared一樣,隻不過第1個實參是個用以動态配置設定記憶體的配置設定器對象。
//make_unique的模拟實作
template<typename T, typename...Ts>
std::unique_ptr<T> make_unique(Ts&&...params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
//make_shared的實作(GCC編譯器)
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp> make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
std::forward<_Args>(__args)...);
}
std::make_unique和std::make_shared的實作
(二)與new相比,make系列函數的優勢
1. 避免代碼備援:建立智能指針時,被建立對象的類型隻需寫1次。如make_shared<T>(),而用new建立智能指針時,需要寫2次。
2. 異常安全:make系列函數可編寫異常安全代碼,改進了new的異常安全性。
3. 提升性能:編譯器有機會利用更簡潔的資料結構産生更小更快的代碼。使用make_shared<T>時會一次性進行記憶體配置設定,該記憶體單塊(single chunck)既儲存了T對象又儲存與其相關聯的控制塊。而直接使用new表達式,除了為T配置設定一次記憶體,還要為與其關聯的控制塊再進行一次記憶體配置設定。
二. make系列函數的局限
(一)所有的make系列函數都不允許自定義删除器。
(二)make系列函數建立對象時,不能接受{}初始化清單。(這是因為完美轉發的轉發函數是個模闆函數,它利用模闆類型進行推導。是以無法将“{}”推導為initializer_list,具體見《完美轉發》一課)。換言之,make系列隻能将圓括号内的形參完美轉發。
(三)自定義記憶體管理的類(如重載了operator new 和operator delete),不建議使用make_shared來建立。原因如下:
1. 重載operator new和operator delete時,往往用來配置設定和釋放該類精确尺寸(sizeof(T))的記憶體塊。
2. 而make_shared建立的shared_ptr,是一個自定義了配置設定器(std::allocate_shared)和删除器的智能指針,由allocate_shared配置設定的記憶體大小也不等于上述的尺寸,而是在此基礎上加上控制塊的大小。
3. 是以,不建議使用make函數為那些重載了operator new和operator delete的類建立對象。
(四)對象的記憶體可能無法及時回收
1. make_shared 隻配置設定一次記憶體,減少了記憶體配置設定的開銷。使得控制塊和托管對象在同一記憶體塊上配置設定。而控制塊是由shared_ptr和weak_ptr共享的,是以兩者共同管理着這個記憶體塊(托管對象+控制塊)。
#include <iostream>
#include <memory> //for smart pointer
#include <vector>
using namespace std;
class Widget
{
public:
Widget(){}
Widget(int x, int y){ cout << "Widget(int x, int y)" << endl; }
Widget(const std::initializer_list<int> li) { cout << "Widget(std::initializer_list<int> li)"<< endl; }
};
void processWidget(std::shared_ptr<Widget> spw, int priority){}
int computePriority() { /*throw 1;*/ return 0; }//假設該函數會抛出異常
class ReallyBigType {};//大對象
int main()
{
//1. make系列函數的優勢
//1.1 避免代碼備援,減少重複書寫類型
auto upw1(std::make_unique<Widget>()); //使用make系列函數,Widget隻需寫一次
std::unique_ptr<Widget> upw2(new Widget); //使用new,Widget需寫二次。
//1.2 make系統異常安全性更高
//在将實參傳遞processWidget前,各個參數時必須先被計算出來,假設順序如下(因編譯器和調用約定而異)
//A. 先new Widget,即一個Widget對象在堆上建立。
//B. 執行computePriority,但假設此時該函數産生異常,那上面的堆對象就會洩漏。
//C. 正常流程下,應執行shared_ptr構造函數,但由于第2步的異常,使得第1步配置設定的堆對象永遠不會被這個
// shared_ptr接管(實際上該shared_ptr自己都沒有機會建立),于是資源洩漏!
processWidget(shared_ptr<Widget>(new Widget), computePriority());//潛在資源洩漏!
//異常安全!
processWidget(make_shared<Widget>(), computePriority()); //如果make_shared首先被調用當computePriority
//發生異常時,則之前的shared_ptr會被釋放,
//進而釋放Widget對象。如果computePriority先
//調用,則make_shared沒有機會被調用,也就不會
//有資源洩漏!
//1.3 make_shared一次性配置設定記憶體
auto spw1 = std::make_shared<Widget>(); //一次性配置設定一個記憶體單塊,可容納Widget對象和控制塊記憶體
std::shared_ptr<Widget> spw2(new Widget); //2次配置設定:new和配置設定控制塊各一次。
//2. make系列函數的局限性
//2.1 make不能自定義删除器
auto widgetDeleter = [](Widget* pw) {delete pw; };
std::unique_ptr<Widget, decltype(widgetDeleter)> upw3(new Widget, widgetDeleter);
std::shared_ptr<Widget> spw3(new Widget, widgetDeleter);
//2.2 make系列函數不能接受{}初始化
auto upv = std::make_unique<std::vector<int>>(10, 20); //10個元素,每個都是20。而不是隻有兩個元素
auto spv = std::make_shared<std::vector<int>>(10, 20); //同上
auto pw1 = new Widget(10, 20); //使用圓括号,比對Widget(int x, int y)
auto pw2 = new Widget{ 10, 20 }; //使用大括号,比對Widget(initializer_list)
delete pw1;
delete pw2;
auto spw = std::make_shared<Widget>(10, 20); //使用圓括号,比對Widget中非initializer_list形參的構造函數
//auto spw = std::make_shared<Widget>({10,20}); //error,make無法轉發大括号初始化清單(原因見《完美轉發》一課)
auto initList = { 10, 20 }; //initList推導為initializer_list<int>
auto splst = std::make_shared<Widget>(initList); //ok,比對Widget(const std::initializer_list<int> li)
//2.3 對象的記憶體可能無法及時回收
auto pBigObj = std::make_shared<ReallyBigType>(); //通過make_shared建立大對象
//... //建立指向大對象的多個std::shared_ptr和std::weak_ptr,并使用這些智能指針來操作對象
//... //最後一個指向大對象的shard_ptr在此析構,但若幹weak_ptr仍然存在
//... //此時,記憶體塊隻析構,還沒回收。因為weak_ptr還共享着記憶體塊中的控制塊
//... //最後一個指向大對象的weak_ptr析構,記憶體塊(托管對象+控制塊)才被回收。由于weak_ptr的生命期比shared_ptr長,
//出現了記憶體塊延遲回收的現象。
//使用new方法則不會出現上述現象
shared_ptr<ReallyBigType> pBigObj2(new ReallyBigType); //通過new,而不是make_shared建立
//... //同前,建立指向多個指向大對象的shared_ptr和weak_ptr。
//... //最後一個指向大對象的shard_ptr在此析構,但若幹weak_ptr仍然存在。此時大對象的記憶體由于強引用為0,被回收。
//... //此階段,僅控制塊占用的記憶體處于未回收狀态。
//... //最後一個指向該對象的weak_ptr析構,控制塊被回收。
return 0;
}