C++98/03 :std::auto_ptr
基本用法
#include <memory>
int main()
{
//方法1
std::auto_ptr<int> sp1(new int(8));
//方法2
std::auto_ptr<int> sp2;
sp2.reset(new int(8));
return 0;
}
智能指針對(duì)象sp1和sp2均持有一個(gè)在堆上分配的int對(duì)象,值都是8,這兩塊堆內(nèi)存都在sp1和sp2釋放時(shí)得到釋放。這是std::auto_ptr的基本用法。
缺陷:
std::auto_ptr真正容易讓人誤用的地方是其不常用的復(fù)制語(yǔ)義,即當(dāng)復(fù)制一個(gè)std::auto_ptr對(duì)象時(shí)(拷貝復(fù)制或operator =復(fù)制),原對(duì)象所持有的堆內(nèi)存對(duì)象也會(huì)轉(zhuǎn)移給復(fù)制出來(lái)的對(duì)象。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//測(cè)試拷貝構(gòu)造
std::auto_ptr<int> sp1(new int(8));
std::auto_ptr<int> sp2(sp1);
if (sp1.get() != NULL)
{
std::cout << "sp1 is not empty." << std::endl;
}
else
{
std::cout << "sp1 is empty." << std::endl;
}
if (sp2.get() != NULL)
{
std::cout << "sp2 is not empty." << std::endl;
}
else
{
std::cout << "sp2 is empty." << std::endl;
}
//測(cè)試賦值構(gòu)造
std::auto_ptr<int> sp3(new int(8));
std::auto_ptr<int> sp4;
sp4 = sp3;
if (sp3.get() != NULL)
{
std::cout << "sp3 is not empty." << std::endl;
}
else
{
std::cout << "sp3 is empty." << std::endl;
}
if (sp4.get() != NULL)
{
std::cout << "sp4 is not empty." << std::endl;
}
else
{
std::cout << "sp4 is empty." << std::endl;
}
return 0;
}
所以我們不能使用這樣的代碼
std::vector<std::auto_ptr<int>> v1;
當(dāng)用算法對(duì)容器操作的時(shí)候(如最常見(jiàn)的容器元素遍歷),很難避免不對(duì)容器中的元素實(shí)現(xiàn)賦值傳遞,這樣便會(huì)使容器中多個(gè)元素被置為空指針,這不是我們希望看到的,可能會(huì)造成一些意想不到的錯(cuò)誤。
以史為鑒,作為std::auto_ptr的替代者std::unique_ptr吸取了這個(gè)經(jīng)驗(yàn)教訓(xùn)
正因?yàn)閟td::auto_ptr的設(shè)計(jì)存在如此重大缺陷,C++11標(biāo)準(zhǔn)在充分借鑒和吸收了boost庫(kù)中智能指針的設(shè)計(jì)思想,引入了三種類(lèi)型的智能指針,即std::unique_ptr、std::shared_ptr和std::weak_ptr。
C++ 11 std::unique_ptr
定義:
std::unique_ptr對(duì)其持有的堆內(nèi)存具有唯一擁有權(quán),也就是說(shuō)引用計(jì)數(shù)永遠(yuǎn)是1,std::unique_ptr對(duì)象銷(xiāo)毀時(shí)會(huì)釋放其持有的堆內(nèi)存??梢允褂靡韵路绞匠跏蓟粋€(gè)std::unique_ptr對(duì)象:
//初始化方式1
std::unique_ptr<int> sp1(new int(123));
//初始化方式2
std::unique_ptr<int> sp2;
sp2.reset(new int(123));
//初始化方式3
std::unique_ptr<int> sp3 = std::make_unique<int>(123);
推薦使用初始化方式3:為什么?
參考[C++11 make_shared](http://www.itdecent.cn/p/03eea8262c11)
其中std::make_unique 是C++14才有 C++11沒(méi)有 ,但是可以自己手動(dòng)寫(xiě)
代碼如下:
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::auto_ptr的前車(chē)之鑒,std::unique_ptr禁止復(fù)制語(yǔ)義,為了達(dá)到這個(gè)效果,std::unique_ptr類(lèi)的拷貝構(gòu)造函數(shù)和賦值運(yùn)算符(operator =)被標(biāo)記為 =delete。
template <class T>
class unique_ptr
{
//省略其他代碼..
//拷貝構(gòu)造函數(shù)和賦值運(yùn)算符被標(biāo)記為delete
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
std::unique_ptr<int> sp1(std::make_unique<int>(123));;
//以下代碼無(wú)法通過(guò)編譯
//std::unique_ptr<int> sp2(sp1);
std::unique_ptr<int> sp3;
//以下代碼無(wú)法通過(guò)編譯
//sp3 = sp1;
禁止復(fù)制語(yǔ)義也存在特例,即可以通過(guò)一個(gè)函數(shù)返回一個(gè)std::unique_ptr:
#include <memory>
using namespace std;
std::unique_ptr<int> func(int val)
{
std::unique_ptr<int> up(new int(val));
return up;
}
int main()
{
std::unique_ptr<int> sp1 = func(123);
return 0;
}
既然std::unique_ptr不能復(fù)制,那么如何將一個(gè)std::unique_ptr對(duì)象持有的堆內(nèi)存轉(zhuǎn)移給另外一個(gè)呢?答案是使用移動(dòng)構(gòu)造,示例代碼如下:
#include <memory>
int main()
{
std::unique_ptr<int> sp1(std::make_unique<int>(123));
std::unique_ptr<int> sp2(std::move(sp1));
std::unique_ptr<int> sp3;
sp3 = std::move(sp2);
return 0;
}
以上代碼利用std::move將sp1持有的堆內(nèi)存(值為123)轉(zhuǎn)移給sp2,再把sp2轉(zhuǎn)移給sp3。最后,sp1和sp2不再持有堆內(nèi)存的引用,變成一個(gè)空的智能指針對(duì)象。并不是所有的對(duì)象的std::move操作都有意義,只有實(shí)現(xiàn)了移動(dòng)構(gòu)造函數(shù)(Move Constructor)或移動(dòng)賦值運(yùn)算符(operator =)的類(lèi)才行,而std::unique_ptr正好實(shí)現(xiàn)了這二者,以下是實(shí)現(xiàn)偽碼:
template<typename T, typename Deletor>
class unique_ptr
{
//其他函數(shù)省略...
public:
unique_ptr(unique_ptr&& rhs)
{
this->m_pT = rhs.m_pT;
//源對(duì)象釋放
rhs.m_pT = nullptr;
}
unique_ptr& operator=(unique_ptr&& rhs)
{
this->m_pT = rhs.m_pT;
//源對(duì)象釋放
rhs.m_pT = nullptr;
return *this;
}
private:
T* m_pT;
};
這是std::unique_ptr具有移動(dòng)語(yǔ)義的原因
std::unique_ptr不僅可以持有一個(gè)堆對(duì)象,也可以持有一組堆對(duì)象,示例如下:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//創(chuàng)建10個(gè)int類(lèi)型的堆對(duì)象
//形式1
std::unique_ptr<int[]> sp1(new int[10]);
//形式2
std::unique_ptr<int[]> sp2;
sp2.reset(new int[10]);
//形式3
std::unique_ptr<int[]> sp3(std::make_unique<int[]>(10));
for (int i = 0; i < 10; ++i)
{
sp1[i] = i;
sp2[i] = i;
sp3[i] = i;
}
for (int i = 0; i < 10; ++i)
{
std::cout << sp1[i] << ", " << sp2[i] << ", " << sp3[i] << std::endl;
}
return 0;
}
程序執(zhí)行結(jié)果如下:
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# g++ -o test_unique_ptr_int test_unique_ptr_int.cpp
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./test
test1_error_ex test_left test_unique_ptr_int
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./test
test1_error_ex test_left test_unique_ptr_int
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./test_unique_ptr_int
0, 0, 0
1, 1, 1
2, 2, 2
3, 3, 3
4, 4, 4
5, 5, 5
6, 6, 6
7, 7, 7
8, 8, 8
9, 9, 9
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr#
std::shared_ptr和std::weak_ptr也可以持有一組堆對(duì)象,用法與std::unique_ptr相同
Deletor:自定義智能指針對(duì)象持有的資源的釋放函數(shù)
默認(rèn)情況下,智能指針對(duì)象在析構(gòu)時(shí)只會(huì)釋放其持有的堆內(nèi)存(調(diào)用delete 或者delete[]),但是假設(shè)這塊堆內(nèi)存代表的對(duì)象還對(duì)應(yīng)一種需要回收的資源(如操作系統(tǒng)的套接字句柄、文件句柄等),我們可以通過(guò)自定義智能指針的資源釋放函數(shù)。假設(shè)現(xiàn)在有一個(gè)Socket類(lèi),對(duì)應(yīng)著操作系統(tǒng)的套接字句柄,在回收時(shí)需要關(guān)閉該對(duì)象,我們可以如下自定義智能指針對(duì)象的資源析構(gòu)函數(shù),這里以std::unique_ptr為例:
#include <iostream>
#include <memory>
using namespace std;
class Socket
{
public:
Socket()
{
}
~Socket()
{
}
//關(guān)閉資源句柄
void close()
{
}
};
int main()
{
auto deletor = [](Socket* pSocket) {
//關(guān)閉句柄
pSocket->close();
//TODO: log日志
delete pSocket;
};
std::unique_ptr<Socket, void(*)(Socket * pSocket)> spSocket(new Socket(), deletor);
return 0;
}
自定義std::unique_ptr的資源釋放函數(shù)其規(guī)則是:
std::unique_ptr<T, DeletorFuncPtr>
其中T是你要釋放的對(duì)象類(lèi)型,DeletorFuncPtr是一個(gè)自定義函數(shù)指針。
上面寫(xiě)的有點(diǎn)麻煩,可以通過(guò)類(lèi)型推導(dǎo)decltype進(jìn)行簡(jiǎn)化
std::unique_ptr<Socket, decltype(deletor)> spSocket(new Socket(), deletor);
C++ std::shared_ptr
std::unique_ptr對(duì)其持有的資源具有獨(dú)占性,而std::shared_ptr持有的資源可以在多個(gè)std::shared_ptr之間共享,每多一個(gè)std::shared_ptr對(duì)資源的引用,資源引用計(jì)數(shù)將增加1,每一個(gè)指向該資源的std::shared_ptr對(duì)象析構(gòu)時(shí),資源引用計(jì)數(shù)減1,最后一個(gè)std::shared_ptr對(duì)象析構(gòu)時(shí),發(fā)現(xiàn)資源計(jì)數(shù)為0,將釋放其持有的資源。多個(gè)線程之間,遞增和減少資源的引用計(jì)數(shù)是安全的。(注意:這不意味著多個(gè)線程同時(shí)操作std::shared_ptr引用的對(duì)象是安全的)。std::shared_ptr提供了一個(gè)use_count()方法來(lái)獲取當(dāng)前持有資源的引用計(jì)數(shù)。除了上面描述的,std::shared_ptr用法和std::unique_ptr基本相同。
初始化:
//初始化方式1
std::shared_ptr<int> sp1(new int(123));
//初始化方式2
std::shared_ptr<int> sp2;
sp2.reset(new int(123));
//初始化方式3
std::shared_ptr<int> sp3;
sp3 = std::make_shared<int>(123);
和std::unique_ptr一樣,應(yīng)該優(yōu)先使用std::make_shared去初始化一個(gè)std::shared_ptr對(duì)象。
看下面代碼
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A()
{
std::cout << "A constructor" << std::endl;
}
~A()
{
std::cout << "A destructor" << std::endl;
}
};
int main()
{
{
//初始化方式1
std::shared_ptr<A> sp1(new A());
std::cout << "use count: " << sp1.use_count() << std::endl;
//初始化方式2
std::shared_ptr<A> sp2(sp1);
std::cout << "use count: " << sp1.use_count() << std::endl;
sp2.reset();
std::cout << "use count: " << sp1.use_count() << std::endl;
{
std::shared_ptr<A> sp3 = sp1;
std::cout << "use count: " << sp1.use_count() << std::endl;
}
std::cout << "use count: " << sp1.use_count() << std::endl;
}
return 0;
}
結(jié)果如下:
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./test_shared_ptr_use_count
A constructor
use count: 1
use count: 2
use count: 1
use count: 2
use count: 1
A destructor
std::enable_shared_from_this
有時(shí)候需要在類(lèi)中返回包裹當(dāng)前對(duì)象(this)的一個(gè)std::shared_ptr對(duì)象給外部使用,C++新標(biāo)準(zhǔn)也為我們考慮到了這一點(diǎn),有如此需求的類(lèi)只要繼承自std::enable_shared_from_this<T>模板對(duì)象即可。用法如下:
#include <iostream>
#include <memory>
using namespace std;
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
std::cout << "A constructor" << std::endl;
}
~A()
{
std::cout << "A destructor" << std::endl;
}
std::shared_ptr<A> getSelf()
{
return shared_from_this();
}
};
int main()
{
std::shared_ptr<A> sp1(new A());
std::shared_ptr<A> sp2 = sp1->getSelf();
std::cout << "use count: " << sp1.use_count() << std::endl;
return 0;
}
運(yùn)行結(jié)果如下:
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./test_shared_ptr_from_this
A constructor
use count: 2
A destructor
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr#
上述代碼中,類(lèi)A繼承自std::enable_shared_from_this<A>并提供一個(gè)getSelf()方法返回自身的std::shared_ptr對(duì)象,在getSelf()中調(diào)用shared_from_this()即可。
std::enable_shared_from_this用起來(lái)比較方便,但是也存在很多不易察覺(jué)的陷阱。
陷阱一:不應(yīng)該共享?xiàng)?duì)象的 this 給智能指針對(duì)象
//其他相同代碼省略...
int main()
{
A a;
std::shared_ptr<A> sp2 = a.getSelf();
std::cout << "use count: " << sp2.use_count() << std::endl;
return 0;
}
運(yùn)行修改后的代碼會(huì)發(fā)現(xiàn)程序在std::shared_ptr<A> sp2 = a.getSelf();產(chǎn)生崩潰。這是因?yàn)椋悄苤羔樄芾淼氖嵌褜?duì)象,棧對(duì)象會(huì)在函數(shù)調(diào)用結(jié)束后自行銷(xiāo)毀,因此不能通過(guò)shared_from_this()將該對(duì)象交由智能指針對(duì)象管理。切記:智能指針最初設(shè)計(jì)的目的就是為了管理堆對(duì)象的(即那些不會(huì)自動(dòng)釋放的資源)
陷阱一:不應(yīng)該共享?xiàng)?duì)象的 this 給智能指針對(duì)象
#include <iostream>
#include <memory>
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
m_i = 9;
//注意:
//比較好的做法是在構(gòu)造函數(shù)里面調(diào)用shared_from_this()給m_SelfPtr賦值
//但是很遺憾不能這么做,如果寫(xiě)在構(gòu)造函數(shù)里面程序會(huì)直接崩潰
std::cout << "A constructor" << std::endl;
}
~A()
{
m_i = 0;
std::cout << "A destructor" << std::endl;
}
void func()
{
m_SelfPtr = shared_from_this();
}
public:
int m_i;
std::shared_ptr<A> m_SelfPtr;
};
int main()
{
{
std::shared_ptr<A> spa(new A());
spa->func();
}
return 0;
}
結(jié)果如下
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./shared_ptr_trap1
A constructor
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr#
在程序的整個(gè)生命周期內(nèi),只有A類(lèi)構(gòu)造函數(shù)的調(diào)用輸出,沒(méi)有A類(lèi)析構(gòu)函數(shù)的調(diào)用輸出,這意味著new出來(lái)的A對(duì)象產(chǎn)生了內(nèi)存泄漏了!
我們來(lái)分析一下new出來(lái)的A對(duì)象為什么得不到釋放。當(dāng)程序執(zhí)行到42行后,spa出了其作用域準(zhǔn)備析構(gòu),在析構(gòu)時(shí)其發(fā)現(xiàn)仍然有另外的一個(gè)std::shared_ptr對(duì)象即A::m_SelfPtr 引用了A,因此spa只會(huì)將A的引用計(jì)數(shù)遞減為1,然后就銷(xiāo)毀自身了。現(xiàn)在留下一個(gè)矛盾的處境:必須銷(xiāo)毀A才能銷(xiāo)毀其成員變量m_SelfPtr,而銷(xiāo)毀A必須先銷(xiāo)毀m_SelfPtr。這就是所謂的std::enable_shared_from_this的循環(huán)引用問(wèn)題。
我們?cè)趯?shí)際開(kāi)發(fā)中應(yīng)該避免做出這樣的邏輯設(shè)計(jì),這種情形下即使使用了智能指針也會(huì)造成內(nèi)存泄漏。也就是說(shuō)一個(gè)資源的生命周期可以交給一個(gè)智能指針對(duì)象,但是該智能指針的生命周期不可以再交給該資源本身來(lái)管理。
C++ std::weak_ptr
std::weak_ptr是一個(gè)不控制資源生命周期的智能指針,是對(duì)對(duì)象的一種弱引用,只是提供了對(duì)其管理的資源的一個(gè)訪問(wèn)手段,引入它的目的為協(xié)助std::shared_ptr工作。
std::weak_ptr可以從一個(gè)std::shared_ptr或另一個(gè)std::weak_ptr對(duì)象構(gòu)造,std::shared_ptr可以直接賦值給std::weak_ptr ,也可以通過(guò)std::weak_ptr的lock()函數(shù)來(lái)獲得std::shared_ptr。它的構(gòu)造和析構(gòu)不會(huì)引起引用計(jì)數(shù)的增加或減少。std::weak_ptr可用來(lái)解決std::shared_ptr相互引用時(shí)的死鎖問(wèn)題,即兩個(gè)std::shared_ptr相互引用,那么這兩個(gè)指針的引用計(jì)數(shù)永遠(yuǎn)不可能下降為0, 資源永遠(yuǎn)不會(huì)釋放。
初始化:
#include <iostream>
#include <memory>
int main()
{
//創(chuàng)建一個(gè)std::shared_ptr對(duì)象
std::shared_ptr<int> sp1(new int(123));
std::cout << "use count: " << sp1.use_count() << std::endl;
//通過(guò)構(gòu)造函數(shù)得到一個(gè)std::weak_ptr對(duì)象
std::weak_ptr<int> sp2(sp1);
std::cout << "use count: " << sp1.use_count() << std::endl;
//通過(guò)賦值運(yùn)算符得到一個(gè)std::weak_ptr對(duì)象
std::weak_ptr<int> sp3 = sp1;
std::cout << "use count: " << sp1.use_count() << std::endl;
//通過(guò)一個(gè)std::weak_ptr對(duì)象得到另外一個(gè)std::weak_ptr對(duì)象
std::weak_ptr<int> sp4 = sp2;
std::cout << "use count: " << sp1.use_count() << std::endl;
return 0;
}
運(yùn)行結(jié)果:
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# g++ -o weak_ptr_test weak_ptr_test.cpp root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./weak_ptr_test
use count: 1
use count: 1
use count: 1
use count: 1
無(wú)論通過(guò)何種方式創(chuàng)建std::weak_ptr都不會(huì)增加資源的引用計(jì)數(shù),因此每次輸出引用計(jì)數(shù)的值都是1。
既然,std::weak_ptr不管理對(duì)象的生命周期,那么其引用的對(duì)象可能在某個(gè)時(shí)刻被銷(xiāo)毀了,如何得知呢?std::weak_ptr提供了一個(gè)expired()方法來(lái)做這一項(xiàng)檢測(cè),返回true,說(shuō)明其引用的資源已經(jīng)不存在了;返回false,說(shuō)明該資源仍然存在,這個(gè)時(shí)候可以使用std::weak_ptr 的lock()方法得到一個(gè)std::shared_ptr對(duì)象然后繼續(xù)操作資源,以下代碼演示了該用法:
//tmpConn_是一個(gè)std::weak_ptr<TcpConnection>對(duì)象
//tmpConn_引用的TcpConnection已經(jīng)銷(xiāo)毀,直接返回
if (tmpConn_.expired())
return;
std::shared_ptr<TcpConnection> conn = tmpConn_.lock();
if (conn)
{
//對(duì)conn進(jìn)行操作,省略...
}
既然使用了std::weak_ptr的expired()方法判斷了對(duì)象是否存在,為什么不直接使用std::weak_ptr對(duì)象對(duì)引用資源進(jìn)行操作呢?
實(shí)際上這是行不通的,std::weak_ptr類(lèi)沒(méi)有重寫(xiě)operator->和operator方法,因此不能像std::shared_ptr或std::unique_ptr一樣直接操作對(duì)象,同時(shí)std::weak_ptr類(lèi)也沒(méi)有重寫(xiě)operator bool()操作,因此也不能通過(guò)std::weak_ptr對(duì)象直接判斷其引用的資源是否存在:
#include <memory>
#include <iostream>
class A
{
public:
void doSomething()
{
std::cout<< "do something" << std::endl;
}
};
int main()
{
std::shared_ptr<A> sp1(new A());
std::weak_ptr<A> sp2(sp1);
//正確代碼
if (sp1)
{
//正確代碼
sp1->doSomething();
(*sp1).doSomething();
}
//正確代碼
if (!sp1)
{
}
//錯(cuò)誤代碼,無(wú)法編譯通過(guò)
//if (sp2)
//{
// //錯(cuò)誤代碼,無(wú)法編譯通過(guò)
// sp2->doSomething();
// (*sp2).doSomething();
//}
//錯(cuò)誤代碼,無(wú)法編譯通過(guò)
//if (!sp2)
//{
//}
return 0;
}
結(jié)果:
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# g++ -o weak_ptr_test2 weak_ptr_test2.cpp
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./weak_ptr_test2do something
do something
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr#
之所以std::weak_ptr不增加引用資源的引用計(jì)數(shù)來(lái)管理資源的生命周期,是因?yàn)榧词顾鼘?shí)現(xiàn)了以上說(shuō)的幾個(gè)方法,調(diào)用它們?nèi)匀皇遣话踩模驗(yàn)樵谡{(diào)用期間,引用的資源可能恰好被銷(xiāo)毀了,這樣可能會(huì)造成比較棘手的錯(cuò)誤和麻煩。
因此,std::weak_ptr的正確使用場(chǎng)景是那些資源如果可用就使用,如果不可用則不使用的場(chǎng)景,它不參與資源的生命周期管理。例如,網(wǎng)絡(luò)分層結(jié)構(gòu)中,Session對(duì)象(會(huì)話對(duì)象)利用Connection對(duì)象(連接對(duì)象)提供的服務(wù)來(lái)進(jìn)行工作,但是Session對(duì)象不管理Connection對(duì)象的生命周期,Session管理Connection的生命周期是不合理的,因?yàn)榫W(wǎng)絡(luò)底層出錯(cuò)會(huì)導(dǎo)致Connection對(duì)象被銷(xiāo)毀,此時(shí)Session對(duì)象如果強(qiáng)行持有Connection對(duì)象則與事實(shí)矛盾。
std::weak_ptr的應(yīng)用場(chǎng)景,經(jīng)典的例子是訂閱者模式或者觀察者模式中。
智能指針的大小
一個(gè)std::unique_ptr對(duì)象大小與裸指針大小相同(即sizeof(std::unique_ptr<T>) == sizeof(void*)),而std::shared_ptr的大小是std::unique_ptr的一倍。以下是我在gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) 上測(cè)試的結(jié)果
#include <iostream>
#include <memory>
#include <string>
int main()
{
std::shared_ptr<int> sp0;
std::shared_ptr<std::string> sp1;
sp1.reset(new std::string());
std::unique_ptr<int> sp2;
std::weak_ptr<int> sp3;
std::cout << "sp0 size: " << sizeof(sp0) << std::endl;
std::cout << "sp1 size: " << sizeof(sp1) << std::endl;
std::cout << "sp2 size: " << sizeof(sp2) << std::endl;
std::cout << "sp3 size: " << sizeof(sp3) << std::endl;
return 0;
}
結(jié)果
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# g++ -o auto_ptr_size auto_ptr_size.cpp root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr# ./auto_ptr_size
sp0 size: 16
sp1 size: 16
sp2 size: 8
sp3 size: 16
root@iZuf65i08ucxtob67o6urwZ:/usr/myC++/shared_ptr#
在32位機(jī)器上,std::unique_ptr占 4 字節(jié),std::shared_ptr和std::weak_ptr占8字節(jié);在64位機(jī)器上,std_unique_ptr占8字節(jié),std::shared_ptr和std::weak_ptr占16字節(jié)。也就是說(shuō),std_unique_ptr的大小總是和原始指針大小一樣,std::shared_ptr和std::weak_ptr大小是原始指針的2倍。
智能指針使用事項(xiàng):
C++新標(biāo)準(zhǔn)提倡的理念之一是不應(yīng)該再手動(dòng)調(diào)用delete或者free函數(shù)去釋放內(nèi)存了,而應(yīng)該把它們交給新標(biāo)準(zhǔn)提供的各種智能指針對(duì)象。C++新標(biāo)準(zhǔn)中的各種智能指針是如此的實(shí)用與強(qiáng)大,在現(xiàn)代C++ 項(xiàng)目開(kāi)發(fā)中,讀者應(yīng)該盡量去使用它們。智能指針雖然好用,但稍不注意,也可能存在許多難以發(fā)現(xiàn)的bug,這里我根據(jù)經(jīng)驗(yàn)總結(jié)了幾條:
1.一旦一個(gè)對(duì)象使用智能指針管理后,就不該再使用原始裸指針去操作;
#include <memory>
class Subscriber
{
//省略具體實(shí)現(xiàn)
};
int main()
{
Subscriber* pSubscriber = new Subscriber();
std::unique_ptr<Subscriber> spSubscriber(pSubscriber);
delete pSubscriber;
return 0;
}
記住,一旦智能指針對(duì)象接管了你的資源,所有對(duì)資源的操作都應(yīng)該通過(guò)智能指針對(duì)象進(jìn)行,不建議再通過(guò)原始指針進(jìn)行操作了。當(dāng)然,除了std::weak_ptr,std::unique_ptr和std::shared_ptr都提供了獲取原始指針的方法——get()函數(shù)。
int main()
{
Subscriber* pSubscriber = new Subscriber();
std::unique_ptr<Subscriber> spSubscriber(pSubscriber);
//pTheSameSubscriber和pSubscriber指向同一個(gè)對(duì)象
Subscriber* pTheSameSubscriber= spSubscriber.get();
return 0;
}
2. 分清楚場(chǎng)合應(yīng)該使用哪種類(lèi)型的智能指針
通常情況下,如果你的資源不需要在其他地方共享,那么應(yīng)該優(yōu)先使用std::unique_ptr,反之使用std::shared_ptr,當(dāng)然這是在該智能指針需要管理資源的生命周期的情況下;如果不需要管理對(duì)象的生命周期,請(qǐng)使用std::weak_ptr。
3. 認(rèn)真考慮,避免操作某個(gè)引用資源已經(jīng)釋放的智能指針
#include <iostream>
#include <memory>
class T
{
public:
void doSomething()
{
std::cout << "T do something..." << m_i << std::endl;
}
private:
int m_i;
};
int main()
{
std::shared_ptr<T> sp1(new T());
const auto& sp2 = sp1;
sp1.reset();
//由于sp2已經(jīng)不再持有對(duì)象的引用,程序會(huì)在這里出現(xiàn)意外的行為
sp2->doSomething();
return 0;
}
上述代碼中,sp2是sp1的引用,sp1被置空后,sp2也一同為空。這時(shí)候調(diào)用sp2->doSomething(),sp2->(即 operator->)在內(nèi)部會(huì)調(diào)用get()方法獲取原始指針對(duì)象,這時(shí)會(huì)得到一個(gè)空指針(地址為0),繼續(xù)調(diào)用doSomething()導(dǎo)致程序崩潰。