美文网首页
C++智能指针之unique_ptr

C++智能指针之unique_ptr

作者: NullBugs | 来源:发表于2019-08-28 17:09 被阅读0次

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://www.jianshu.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/

    相关文章

      网友评论

          本文标题:C++智能指针之unique_ptr

          本文链接:https://www.haomeiwen.com/subject/cqlmectx.html