C++智能指針之unique_ptr

版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明徒溪。
本文鏈接:http://www.reibang.com/p/b8d6b10da667

智能指針

1.什么是智能指針

最近有個(gè)段子

  • C語言:搬石頭砸自己的腳梳侨;
  • C++:搬石頭砸自己的腳,也可能砸別人的腳材蛛;
  • python:點(diǎn)個(gè)按鈕圆到,自動(dòng)搬石頭;

在三大常用語言中卑吭,C/C++, java芽淡,python中,通常情況下C/C++性能最好豆赏,但是大部分開發(fā)這都喜歡java和python挣菲,其中主要的原因之一是C/C++缺少智能內(nèi)存回收,在復(fù)雜的系統(tǒng)中掷邦,經(jīng)常遇到一個(gè)常見的問題 -- 內(nèi)存泄露白胀。

對(duì)于一個(gè)程序員,碼農(nóng)最頭大的事情就是內(nèi)存泄露抚岗。君不見或杠,內(nèi)存泄露吼三吼。

C++的開發(fā)這都會(huì)想苟跪,有沒有一種方式能像java和python一樣方便廷痘,系統(tǒng)自動(dòng)釋放呢?
unique_ptr 是C++ 11 提供的用于防止內(nèi)存泄漏的智能指針中的一種實(shí)現(xiàn),獨(dú)享被管理對(duì)象指針?biāo)袡?quán)的智能指針(shared_ptr 下次在分享)件已。

2.智能指針的實(shí)現(xiàn)

unique_ptr定義<memory>頭文件中

template <class T, class D = default_delete<T>> class unique_ptr;
template <class T, class D> class unique_ptr<T[],D>;

std::unique_ptr 是通過指針占有并管理另一對(duì)象笋额,并在 unique_ptr 離開作用域時(shí)釋放該對(duì)象的智能指針。

在下列兩者之一發(fā)生時(shí)用關(guān)聯(lián)的刪除器釋放對(duì)象:

  • 銷毀了管理的 unique_ptr 對(duì)象
  • 通過 operator=reset() 賦值另一指針給管理的 unique_ptr 對(duì)象篷扩。

通過調(diào)用 get_deleter()(ptr) 兄猩,用潛在為用戶提供的刪除器釋放對(duì)象。默認(rèn)刪除器用 delete 運(yùn)算符鉴未,它銷毀對(duì)象并解分配內(nèi)存枢冤。

unique_ptr 亦可以不占有對(duì)象,該情況下稱它為空 (empty)铜秆。

std::unique_ptr 有兩個(gè)版本:

  1. 管理個(gè)對(duì)象(例如以 new 分配)

  2. 管理動(dòng)態(tài)分配的對(duì)象數(shù)組(例如以 new[] 分配)

類滿足可移動(dòng)構(gòu)造 (MoveConstructible) 和可移動(dòng)賦值 (MoveAssignable) 的要求淹真,但不滿足可復(fù)制構(gòu)造 (CopyConstructible) 或可復(fù)制賦值 (CopyAssignable) 的要求。

unique_ptr的使用

備注:make_unique 是C++14的新特性连茧,如果使用C++11編譯核蘸,請(qǐng)把make_unique改成std::unique_ptr<T>的形式巍糯。

#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>

struct B {
    virtual void bar() { std::cout << "B::bar\n"; }
    virtual ~B() = default;
};
struct D : B
{
    D() { std::cout << "D::D\n";  }
    ~D() { std::cout << "D::~D\n";  }
    void bar() override { std::cout << "D::bar\n";  }
};

// 消費(fèi) unique_ptr 的函數(shù)能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}

void close_file(std::FILE* fp) { std::fclose(fp); }

int main()
{
    std::cout << "unique ownership semantics demo\n";
    {
        auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
        auto q = pass_through(std::move(p));
        assert(!p); // 現(xiàn)在 p 不占有任何內(nèi)容并保有空指針
        q->bar();   // 而 q 占有 D 對(duì)象
    } // ~D 調(diào)用于此

    std::cout << "Runtime polymorphism demo\n";
    {
        std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
        // 作為指向基類的指針
        p->bar(); // 虛派發(fā)

        std::vector<std::unique_ptr<B>> v;  // unique_ptr 能存儲(chǔ)于容器
        v.push_back(std::make_unique<D>());
        v.push_back(std::move(p));
        v.emplace_back(new D);
        for(auto& p: v) p->bar(); // 虛派發(fā)
    } // ~D called 3 times

    std::cout << "Custom deleter demo\n";
    std::ofstream("demo.txt") << 'x'; // 準(zhǔn)備要讀的文件
    {
        std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),
                                                             close_file);
        if(fp) // fopen 可以打開失敗客扎;該情況下 fp 保有空指針
            std::cout << (char)std::fgetc(fp.get()) << '\n';
    } // fclose() 調(diào)用于此祟峦,但僅若 FILE* 不是空指針
    // (即 fopen 成功)

    std::cout << "Custom lambda-expression deleter demo\n";
    {
        std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr)
        {
            std::cout << "destroying from a custom deleter...\n";
            delete ptr;
        });  // p 占有 D
        p->bar();
    } // 調(diào)用上述 lambda 并銷毀 D

    std::cout << "Array form of unique_ptr demo\n";
    {
        std::unique_ptr<D[]> p{new D[3]};
    } // 調(diào)用 ~D 3 次
}

運(yùn)行結(jié)果:

unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D

智能指針使用總結(jié):

  1. 智能指針自己管理內(nèi)存的聲明周期;
  2. 智能指針在構(gòu)造是可以制定對(duì)應(yīng)了銷毀函數(shù)徙鱼;

進(jìn)階用法:

在上述的智能指針使用中宅楞,我們通過傳入delete的函數(shù),這里可以采用更優(yōu)雅的方式:通過結(jié)構(gòu)體的運(yùn)算符重載達(dá)到delete函數(shù)的效果

#include <iostream>
#include <memory>
using namespace std;

typedef struct _package{
    unsigned char* data;
    int length;
}package_t;

void release_package(package_t* package){
    cout<<"release_package"<<endl;
    if(!package){
        return;
    }
    if(package->data){
        delete[] package->data;
    }
    delete package;
}

struct package_destuctor{
    void operator()(package_t* package){
        release_package(package);
    }
};


// unique_ptr 不能拷貝或者復(fù)制
int main(){
    unique_ptr<package_t, decltype(release_package)*> ret1(new package_t(), release_package);
    unique_ptr<package_t, package_destuctor> ret(new package_t());
    return 0;
}

運(yùn)行結(jié)果:
兩種方式都正常release數(shù)據(jù)

release_package
release_package

進(jìn)階二

unique_ptr作為形參時(shí)袱吆,必須保證不能發(fā)生COPY
例如:

unique_ptr<T> uptr(new T);
show_ptr(uptr);

這種調(diào)用會(huì)出錯(cuò)厌衙,原因在于uptr作為形參時(shí),會(huì)發(fā)生copy绞绒,而unique_ptr不允許Copy迅箩。

#include <iostream>
#include <memory>

using namespace std;

class MyTest{
public:
    MyTest(const string & name)
        :_name(name){
        cout<<"MyTest:"<<_name<<endl;
    }

    MyTest(const MyTest & another){
        _name = another._name;
        cout<<another._name<<"copyStruct "<<_name<<endl;
    }

    MyTest & operator =(const MyTest & another){
        if(&another==this)
            return *this;
        this->_name=another._name;
        cout<<another._name<<"copyAssin to "<<_name<<endl;
    }

    ~MyTest(){
        cout<<"~MyTest:"<<_name<<endl;
    }

//private:
    string _name;
};


//!例外:
//①返回一個(gè)即將被銷毀的uniptr
unique_ptr<MyTest> retDying(string param){
    return unique_ptr<MyTest>(new MyTest(param));
}

//②返回一個(gè)局部對(duì)象;
unique_ptr<MyTest> retTemp(string param){
    unique_ptr<MyTest> pTemp(new MyTest(param));
    return pTemp;
}

//unique_ptr可以作為形參,必須保證不能發(fā)生copy
unique_ptr<int> show(unique_ptr<int> up){
    cout<<*up<<endl;
    return up;
}

//不能刪除unique中的指針,如果刪除,智能指針會(huì)報(bào)錯(cuò)
void release(unique_ptr<MyTest>& ptr){
    if(!ptr){
        cout<<"delete_ptr ptr is null"<<endl;
        return;
    }
    auto p = ptr.get();
    //delete p; 
}
// unique_ptr 不能拷貝或者復(fù)制
int main(){
    unique_ptr<MyTest> ret1 = retDying("dying");
    cout<<(*ret1)._name<<endl;

    unique_ptr<int> pCount(new int(10));
    //unique_ptr可以作為形參,必須保證不能發(fā)生copy,pCount不能當(dāng)做參數(shù),可以使用轉(zhuǎn)移或者move
    show(unique_ptr<int>(new int(10)));
    show(move(pCount));

    unique_ptr<MyTest> ret2 = retTemp("temp");
    cout<<(*ret2)._name<<endl;
    //如果傳ret,必須聲明為引用,如果不聲明引用,必須則不能使用ret1作為參數(shù), 可以u(píng)nique_ptr<int> retp(ret1.release())
    release(ret1);
    return 0;
}

結(jié)果如下:

MyTest:dying
dying
10
10
MyTest:temp
temp
~MyTest:temp
~MyTest:dying

unique_ptr之release重點(diǎn)聲明

unique_ptr中的release方法处铛,第一眼看過去,是釋放內(nèi)存拐揭,其實(shí)并不是撤蟆,并不是,并不是堂污,重要的事情說三遍
看unique_ptr的release函數(shù)聲明:

pointer release() noexcept;

其功能是當(dāng)前的智能指針釋放對(duì)持有指針的控制家肯,并返回持有的指針,其含義是:release之后盟猖,大爺不管了讨衣,返回的指針你自己玩吧, 千萬不要想當(dāng)然的認(rèn)為是釋放內(nèi)存式镐,只是釋放控制權(quán)7凑颉!娘汞!

參考:

https://zh.cppreference.com/w/cpp/memory/unique_ptr/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歹茶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子你弦,更是在濱河造成了極大的恐慌惊豺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禽作,死亡現(xiàn)場(chǎng)離奇詭異尸昧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旷偿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門烹俗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爆侣,“玉大人,你說我怎么就攤上這事衷蜓±厶幔” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵磁浇,是天一觀的道長斋陪。 經(jīng)常有香客問我,道長置吓,這世上最難降的妖魔是什么无虚? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮衍锚,結(jié)果婚禮上友题,老公的妹妹穿的比我還像新娘。我一直安慰自己戴质,他們只是感情好度宦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著告匠,像睡著了一般戈抄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上后专,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天划鸽,我揣著相機(jī)與錄音,去河邊找鬼戚哎。 笑死裸诽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的型凳。 我是一名探鬼主播丈冬,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼啰脚!你這毒婦竟也來了殷蛇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤橄浓,失蹤者是張志新(化名)和其女友劉穎粒梦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荸实,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匀们,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了准给。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泄朴。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡重抖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祖灰,到底是詐尸還是另有隱情钟沛,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布局扶,位于F島的核電站恨统,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏三妈。R本人自食惡果不足惜畜埋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畴蒲。 院中可真熱鬧悠鞍,春花似錦、人聲如沸模燥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔫骂。三九已至心肪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纠吴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工慧瘤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戴已,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓锅减,卻偏偏與公主長得像糖儡,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怔匣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • C++裸指針的內(nèi)存問題有:1握联、空懸指針/野指針2、重復(fù)釋放3每瞒、內(nèi)存泄漏4金闽、不配對(duì)的申請(qǐng)與釋放 使用智能指針可以有效...
    WalkeR_ZG閱讀 3,098評(píng)論 0 5
  • C++智能指針 原文鏈接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白將閱讀 6,864評(píng)論 2 21
  • 導(dǎo)讀## 最近在補(bǔ)看《C++ Primer Plus》第六版,這的確是本好書剿骨,其中關(guān)于智能指針的章節(jié)解析的非常清晰...
    小敏紙閱讀 2,003評(píng)論 1 12
  • 導(dǎo)語: C++指針的內(nèi)存管理相信是大部分C++入門程序員的夢(mèng)魘代芜,受到Boost的啟發(fā),C++11標(biāo)準(zhǔn)推出了智能指針...
    7ee72f98ad17閱讀 889評(píng)論 0 1
  • 1. 什么是智能指針浓利? 智能指針是行為類似于指針的類對(duì)象挤庇,但這種對(duì)象還有其他功能钞速。 2. 為什么設(shè)計(jì)智能指針? 引...
    MinoyJet閱讀 638評(píng)論 0 1