C++智能指针之unique_ptr

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接://www.greatytc.com/p/b8d6b10da667

智能指针

1.什么是智能指针

最近有个段子

  • C语言:搬石头砸自己的脚;
  • C++:搬石头砸自己的脚,也可能砸别人的脚;
  • python:点个按钮,自动搬石头;

在三大常用语言中,C/C++, java,python中,通常情况下C/C++性能最好,但是大部分开发这都喜欢java和python,其中主要的原因之一是C/C++缺少智能内存回收,在复杂的系统中,经常遇到一个常见的问题 -- 内存泄露

对于一个程序员,码农最头大的事情就是内存泄露。君不见,内存泄露吼三吼。

C++的开发这都会想,有没有一种方式能像java和python一样方便,系统自动释放呢?
unique_ptr 是C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针(shared_ptr 下次在分享)。

2.智能指针的实现

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 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。

在下列两者之一发生时用关联的删除器释放对象:

  • 销毁了管理的 unique_ptr 对象
  • 通过 operator=reset() 赋值另一指针给管理的 unique_ptr 对象。

通过调用 get_deleter()(ptr) ,用潜在为用户提供的删除器释放对象。默认删除器用 delete 运算符,它销毁对象并解分配内存。

unique_ptr 亦可以不占有对象,该情况下称它为空 (empty)

std::unique_ptr 有两个版本:

  1. 管理个对象(例如以 new 分配)

  2. 管理动态分配的对象数组(例如以 new[] 分配)

类满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的要求。

unique_ptr的使用

备注:make_unique 是C++14的新特性,如果使用C++11编译,请把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";  }
};

// 消费 unique_ptr 的函数能以值或以右值引用接收它
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); // 现在 p 不占有任何内容并保有空指针
        q->bar();   // 而 q 占有 D 对象
    } // ~D 调用于此

    std::cout << "Runtime polymorphism demo\n";
    {
        std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
        // 作为指向基类的指针
        p->bar(); // 虚派发

        std::vector<std::unique_ptr<B>> v;  // unique_ptr 能存储于容器
        v.push_back(std::make_unique<D>());
        v.push_back(std::move(p));
        v.emplace_back(new D);
        for(auto& p: v) p->bar(); // 虚派发
    } // ~D called 3 times

    std::cout << "Custom deleter demo\n";
    std::ofstream("demo.txt") << 'x'; // 准备要读的文件
    {
        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() 调用于此,但仅若 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();
    } // 调用上述 lambda 并销毁 D

    std::cout << "Array form of unique_ptr demo\n";
    {
        std::unique_ptr<D[]> p{new D[3]};
    } // 调用 ~D 3 次
}

运行结果:

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

智能指针使用总结:

  1. 智能指针自己管理内存的声明周期;
  2. 智能指针在构造是可以制定对应了销毁函数;

进阶用法:

在上述的智能指针使用中,我们通过传入delete的函数,这里可以采用更优雅的方式:通过结构体的运算符重载达到delete函数的效果

#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 不能拷贝或者复制
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;
}

运行结果:
两种方式都正常release数据

release_package
release_package

进阶二

unique_ptr作为形参时,必须保证不能发生COPY
例如:

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

这种调用会出错,原因在于uptr作为形参时,会发生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;
};


//!例外:
//①返回一个即将被销毁的uniptr
unique_ptr<MyTest> retDying(string param){
    return unique_ptr<MyTest>(new MyTest(param));
}

//②返回一个局部对象;
unique_ptr<MyTest> retTemp(string param){
    unique_ptr<MyTest> pTemp(new MyTest(param));
    return pTemp;
}

//unique_ptr可以作为形参,必须保证不能发生copy
unique_ptr<int> show(unique_ptr<int> up){
    cout<<*up<<endl;
    return up;
}

//不能删除unique中的指针,如果删除,智能指针会报错
void release(unique_ptr<MyTest>& ptr){
    if(!ptr){
        cout<<"delete_ptr ptr is null"<<endl;
        return;
    }
    auto p = ptr.get();
    //delete p; 
}
// unique_ptr 不能拷贝或者复制
int main(){
    unique_ptr<MyTest> ret1 = retDying("dying");
    cout<<(*ret1)._name<<endl;

    unique_ptr<int> pCount(new int(10));
    //unique_ptr可以作为形参,必须保证不能发生copy,pCount不能当做参数,可以使用转移或者move
    show(unique_ptr<int>(new int(10)));
    show(move(pCount));

    unique_ptr<MyTest> ret2 = retTemp("temp");
    cout<<(*ret2)._name<<endl;
    //如果传ret,必须声明为引用,如果不声明引用,必须则不能使用ret1作为参数, 可以unique_ptr<int> retp(ret1.release())
    release(ret1);
    return 0;
}

结果如下:

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

unique_ptr之release重点声明

unique_ptr中的release方法,第一眼看过去,是释放内存,其实并不是,并不是,并不是,重要的事情说三遍
看unique_ptr的release函数声明:

pointer release() noexcept;

其功能是当前的智能指针释放对持有指针的控制,并返回持有的指针,其含义是:release之后,大爷不管了,返回的指针你自己玩吧, 千万不要想当然的认为是释放内存,只是释放控制权!!!

参考:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • C++裸指针的内存问题有:1、空悬指针/野指针2、重复释放3、内存泄漏4、不配对的申请与释放 使用智能指针可以有效...
    WalkeR_ZG阅读 3,087评论 0 5
  • C++智能指针 原文链接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白将阅读 6,853评论 2 21
  • 导读## 最近在补看《C++ Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰...
    小敏纸阅读 1,990评论 1 12
  • 导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针...
    7ee72f98ad17阅读 866评论 0 1
  • 1. 什么是智能指针? 智能指针是行为类似于指针的类对象,但这种对象还有其他功能。 2. 为什么设计智能指针? 引...
    MinoyJet阅读 636评论 0 1