浅谈智能指针auto_ptr/shared_ptr/unique_ptr

时间:2021-01-03 19:44:06

一.智能指针

1.引入

我们通常使用类似new申请一块空间,交由一个指针指向,假如说最后忘记delete,将会造成内存泄露。而智能指针的出现,就是对这种问题的解决方式,智能指针类似指针,却可以用于管理动态分配的内存。本章所解说的是三种智能指针:

(1)C++98提出,C++11摒弃的auto_ptr
C++11新增的
(2)shared_ptr
(3)unique_ptr
在C++里面,三者都被以模板的形式实现,下面是对智能指针的使用

提示:
(1)智能指针的头文件是#include<memory>
(2)由于unique_ptr和shared_ptr是C++11新增的智能指针,所以编译时需要添加支持C++11的参数
g++ xx.cpp -o xx -std=c++11
(3)智能指针在std域里,需要添加名称空间using namespace std,或者在智能指针前加std::

//test1
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Report
{
public:
    Report(const std::string s):str(s)
    {
        cout<<"Object created"<<endl;
    }
    ~Report()
    {
        cout<<"Object deleted"<<endl;
    }
    void comment()const
    {
        cout<<str<<endl;
    }
private:
    std::string str;
};

int main(int argc, char* const argv[])
{
    //test1: use smart ptr
    {
        std::auto_ptr<Report> ps(new Report("using auto_ptr"));
        ps->comment();
    }
    {
        std::shared_ptr<Report> ps(new Report("using shared_ptr"));
        ps->comment();
    }
    {
        std::unique_ptr<Report> ps(new Report("using unique_ptr"));
        ps->comment();
    }
    return 0;
}

执行结果

[root@server10 auto_ptr]# ./t 
Object created
using auto_ptr
Object deleted
Object created
using shared_ptr
Object deleted
Object created
using unique_ptr
Object deleted

在其作用域内,分别调用了构造以及析构函数

2.浅谈智能指针的原理

智能指针模板定义了类似指针的对象,将new得到的空间的地址赋给该对象,这样,当对象过期时,其析构函数将其释放。

3.智能指针应该避免删除非堆内存

string str("hello");
shared_ptr<string> pstr(&str);//NO!
pstr过期时,将delete非堆内存,错误

4.智能指针和常规指针

智能指针的许多方面都类似指针,

(1)你可以对它解除引用操作(*ps)
(2)可以用它访问结构体成员(ps->puffindex)
(3)将它赋给指向相同类型的常规指针
(4)将智能指针对象赋给相同类型的智能指针(不过会牵扯出“三”中赋值的安全性问题)
但是,智能指针是对象,对象在过期时,会调用其析构函数析构掉,而常规指针,当其指向堆内存,最终需要人为的delete

注意:将常规指针赋给智能指针需要进行显示的类型转换,如下所示

    shared_ptr<int> pd;
    int *p = new int;
    pd = shared_ptr<int>(p);

二.3种智能指针的实现策略/实现技术

两个指针指向同一个对象时,程序可能出现删除该对象两次的情况,使得程序core dumpd,也就是我们常说的段错误。之前讲C++赋值语句时,我们说到了深拷贝,这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本。这是一种解决办法,而auto_ptr/shared_ptr/unique_ptr使用的策略有所不同。

(1)auto_ptr和unique_ptrc采用的是ownership(建立所有权)概念,对于特定对象,只能被一个智能指针所拥有,这样,只有拥有该对象的智能指针的析构函数才会删除该对象,然后,要注意的是,赋值操作会转让操作权。
虽然auto_ptr和unique_ptr都采用该策略,但是unique_ptr的策略更严格。当出现上述情况时,程序会编译出错,而auto_ptr则会在执行阶段core dumped。
(2)shared_ptr则采用reference counting(引用计数)的策略,例如,赋值时,计数+1,指针过期时,计数-1.只有当计数为0时,即最后一个指针过期时,才会被析构掉.

每种策略都有其用途。下面通过代码来分析其优劣:

三.智能指针的优劣分析

1.unique_ptr比auto_ptr优秀

1.1优点1:unique_ptr比auto_ptr更安全

(1)auto_ptr会出现的情况

//test2
auto_ptr<Report> p1[5] =
    {
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")), 
        auto_ptr<Report> (new Report("using auto_ptr")) 
    };
    auto_ptr<Report> p2 = p1[2];//#1
    for(int i=0; i<5; ++i)
    {
        p1[i]->comment();
    }

执行结果

[root@server10 auto_ptr]# ./t 
Object created
Object created
Object created
Object created
Object created
using auto_ptr 0
using auto_ptr 1
Segmentation fault (core dumped)

类似test2代码,(p1[2]将Report对象的所有权转交给p2,而p1[2]此时为空指针,这样要打印p1[2]的值时,造成了core dumped的意外)

(2)unique_ptr对该情况的处理

smrtptrs.cpp: In function ‘int main(int, char* const*)’:
smrtptrs.cpp:71:33: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Report; _Dp = std::default_delete<Report>]’
     unique_ptr<Report> p2 = p1[2];
                                 ^
In file included from /usr/include/c++/4.8.2/memory:81:0,
                 from smrtptrs.cpp:3:
/usr/include/c++/4.8.2/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^

将test2中的auto_ptr使用unique_ptr代替后,编译器直接报上述错误,可见编译器认为#1非法,避免了将p1[2]指向无效数据的情况。
这也是容器算法禁止使用auto_ptr,但是允许使用unique_ptr的原因。
因此,unique_ptr比auto_ptr更安全
当然unique_ptr不是不允许赋值,它允许源unique_ptr是个临时右值,如下面这个例子:

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

unique_ptr<string> demo1(const char* s)
{
    cout<<"DEMO1 TEST"<<endl;
    unique_ptr<string> temp(new string(s));
    return temp;
}

auto_ptr<string> demo2(const char* s)
{
    cout<<"DEMO2 TEST"<<endl;
    auto_ptr<string> temp(new string(s));
    return temp;
}
int main(int argc, char* const argv[])
{
#if __UNIQUE_PTR__
    unique_ptr<string> ps;
    ps = demo1("hello");
#endif
    return 0;
}

所以,将一个临时的智能指针赋给另一个智能指针并不会留下危险的悬挂指针

如何安全的重用unique_ptr指针

要安全的重用unique_ptr指针,可给它赋新值。C++为其提供了std::move()方法。

    unique_ptr<string> pu1(new string("nihao"));
    unique_ptr<string> pu2;
    pu2 = std::move(pu1);//move
    cout<<*pu1<<endl;//赋新值

而auto_ptr由于策略没有unique_ptr严格,无需使用move方法

    auto_ptr<string> pu1, pu2;
    pu1 = demo2("Uniquely special");
    pu2 = pu1;
    pu1 = demo2(" and more");
    cout<<*pu2<<*pu1<<endl;

由于unique_ptr使用了C++11新增的移动构造函数和右值引用,所以可以区分安全和不安全的用法。

1.2优点2:unique_ptr相较auto_ptr和unique_ptr,提供了可用于数组的变体

auto_ptr和shared_ptr可以和new一起使用,但不可以和new[]一起使用,但是unique_ptr可以和new[]一起使用

unique_ptr<double[]> pda(new double(5));

2.相较之下,shared_ptr的策略更加机智

由于shared_ptr使用的是引用计数的策略,所以赋值时,不需要考虑源智能指针为空值的情况

    shared_ptr<string> p1(new string("hello"));
    shared_ptr<string> p2;
    p2 = p1;
    cout<<"p1"<<*p1<<endl<<"p2"<<*p2<<endl;

四.选择智能指针

《C++ primer plus》第六版P673有详细讲解,这里简单阐述一下

#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
#include <stdlib.h>
using namespace std;

unique_ptr<int> make_int(int n)
{
    return unique_ptr<int> (new int(n));
}

void show(unique_ptr<int> & pi)     //pass by reference
{
    cout<< *pi <<' ';
}

int main()
{
    vector<unique_ptr<int> > vp(5);
    for(int i = 0; i < vp.size(); ++i)
    {
        vp[i] = make_int(rand() % 1000);//copy temporary unique_ptr
    }
    vp.push_back(make_int(rand() % 1000));//ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);
    unique_ptr<int> pup(make_int(rand() % 1000));
   // shared_ptr<int> spp(pup);//not allowed. pup is lvalue
    shared_ptr<int> spr(make_int(rand() % 1000));

    return 0;
}

(1)当多个对象指向同一个对象的指针时,应选择shared_ptr
(2)用new申请的内存,返回指向这块内存的指针时,选择unique_ptr就不错
(3)在满足unique_ptr要求的条件时,前提是没有不明确的赋值,也可以使用auto_ptr
(4)如上述代码所示,unique_ptr为右值(不准确的说类似无法寻址)时,可以赋给shared_ptr


That’s all!