auto_ptr

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)致程序崩潰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • C++智能指針 原文鏈接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白將閱讀 6,993評(píng)論 2 21
  • 本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成,引用內(nèi)容的版權(quán)歸原始作者所有,僅限于學(xué)習(xí)研究使用,不得用于任何商業(yè)用途。 智...
    深紅的眼眸閱讀 834評(píng)論 0 0
  • 學(xué)c++的人都知道,在c++里面有一個(gè)痛點(diǎn),就是動(dòng)態(tài)內(nèi)存的管理,就我所經(jīng)歷的一些問(wèn)題來(lái)看,很多莫名其妙的問(wèn)題,最后...
    cpp加油站閱讀 892評(píng)論 0 2
  • C#、Java、python和go等語(yǔ)言中都有垃圾自動(dòng)回收機(jī)制,在對(duì)象失去引用的時(shí)候自動(dòng)回收,而且基本上沒(méi)有指針的...
    StormZhu閱讀 3,873評(píng)論 1 15
  • 1、問(wèn)題引入:Java和C#等語(yǔ)言有自己的垃圾回收機(jī)制,.net運(yùn)行時(shí)和java虛擬機(jī)可以管理分配的堆內(nèi)存,在失去...
    Nrocinu閱讀 430評(píng)論 0 0

友情鏈接更多精彩內(nèi)容