在初始化一個unique_ptr或者shared_ptr時,我們最好優先使用std::make_unique和std::make_shared。原因有一下幾點:
-
異常安全性
假設有如下函數聲明:
intcomputePriority();
void processInvestment(std::shared_ptr<Investment> ptr,int priority);
調用processInvestment的代碼如下所示:
processInvestment(std::shared_ptr<Investment>(newInvestment()),computePriority());
由於在C++中函數參數的執行順序是不固定的,所以在上面對函數processInvestment調用中,函數參數的執行順序很可能是這樣的:
-
new Investment
-
computePriority()
-
std::shared_ptr constructor
這種執行順序的風險是,如果在第二步,執行computePriority的過程中出現異常,那麼在第一步中new出來的對象將變得不可訪問,從而造成內存泄漏。通過make_ptr的方式,將new操作和shared_ptr的構造放在一起執行,就不會出現內存泄漏問題了。
-
執行效率(對於shared_ptr而言)
std::shared_ptr<Investment>ptr(new Investment); //方式1,new的方式
在方式1中,會涉及到兩次動態內存分配:
第一次是new Investment時,爲Investment對象分配空間。
第二次是爲控制塊(Control Block)分配空間。
auto pIn = std::make_shared<Investment>();//方式2,make_shared的方式
在方式二中,一次動態內存分配就足夠了,這是由於make_shared會爲Investment對象和控制塊(Control block)一次性分配一大塊內存。由於只有一次內存分配,因而方式二提高了程序的執行效率。
二、make函數的弊端
既然使用make_shared和make_unique有這麼多好處,我們是否就應該處處使用make函數而完全放棄new的方式了呢?當然不是,make函數也存在一些缺點:
1 make函數不支持用戶自定義釋放器。由於make函數有自己的內存分配和析構規則,所以他不適用於那些有自定義分配器和釋放器的對象。
2 make函數不支持大括號初始化方式。對於下面這句代碼:
auto spv = std::make_shared<vector<int>>(10,20);
意爲spv指向一個vector,這個vector有10個元素,每個元素的值都爲20。如果你想實現的是這個vector有兩個元素,分別爲10,20的話,你只能用new的方式。
3內存釋放不夠靈活。
在使用new的方式中,有兩塊獨立的堆內存,一塊存放資源對象,一塊存放控制塊,當資源對象的引用計數爲0的時候,資源對象會被銷燬,它所佔用的內存也會被隨之銷燬。
在使用make函數的方式中,make函數一次性爲資源對象和控制塊分配了一塊內存,當資源對象的引用計數爲0是,對象被銷燬,但是資源對象佔用的內存卻不會被銷燬,只有當控制塊佔用的內存被銷燬是纔會將資源對象所佔內存一併釋放。那麼,控制塊內存什麼時候被釋放呢?這就涉及到控制塊中另一個引用計數了,這個引用計數被稱爲“Weak Count”,其作用是用來計數指向該資源的weak_ptr的數量。當這個weak count的值爲0時,控制塊纔會被釋放。當資源對象非常龐大時,使用make函數的方式將造成不小的資源浪費。