(C++)智能指针的小“自传”

智能指针发家史

从RAII说起

  • RAII , Resource Aquisition Is Initailization , 资源分配即初始化
  • 这是一种编程的观念——以对象管理资源。
  • 为什么我们需要以对象来管理资源呢?大家都知道,作为程序员的我们,虽说大多时候都保持着高度细致的战斗力,但是人无完人,编程到了懈怠期,难免就会出现一些小的错误,比如:忘记释放内存空间,或者因为在长久的维护期中,忽略了goto、return或者捕获异常等会使程序中途跳转的语句,从而忘记delete。
  • 因此,为了确保资源总是被释放,我们需要将资源放入对象之内,当控制流离开其所调用的函数,该对象的析构函数则会自动释放那些资源。
  • 简而言之,就是耍了一个小小的心机,利用了C++的“析构函数自动调用机制”,从而确保资源都得到释放。
  • 于是乎,智能指针由此应运而生。
  • 程序员们从此满面红光地高举智能指针大旗走向罪恶(笑…..

auto_ptr:伤仲永之路

  • 实际上,为了正确地管理资源,人们进行了包括但不仅限于flag、count等尝试,但还是会被野指针或者没有所指向空间的支配权折磨得死去活来。
  • 于是乎,能够进行管理权转移的auto_ptr出现了。
  • 可谓是一举惊天地啊!
  • 虽然auto_ptr出生就自带光环啊,但他后天不努力,资质颇为平庸的说。
  • 其资质平庸具体来说有:不能共享所有权、auto_ptr不能指向数组 、auto_ptr不能作为容器的成员、不能通过赋值操作来初始化auto_ptr 、不能把auto_ptr放入容器等等等等。
  • 我们来具体剖析一下这个小伙子怎么资质平庸的吧!

    官方定义:“This class template provides a limited garbage collection facility for pointers, by allowing pointers to have the elements they point to automatically destroyed when the auto_ptr object is itself destroyed.”

    • auto_ptr自带类似于java中的垃圾回收站机制,风光吧。
    • 在这里举个小栗子,展示一下自动回收~
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main() {
    auto_ptr<string> ps1(new string("Hello, auto_ptr!"));
    cout << "The content is: " << *ps1 << endl;

    return 0;
}
  • 其内核为:管理权的转移。

    Therefore, no two auto_ptr objects should own the same element, since both would try to destruct them at some point. When an assignment operation takes place between two auto_ptr objects, ownership is transferred, which means that the object losing ownership is set to no longer point to the element (it is set to the null pointer).

  • 更多接口操作:
    这里写图片描述

但是我们注意到,在C++11标准中,虽然为了更好的向前兼容,并没有取消auto_ptr,其实质上其实是被摒弃了。

  • 为什么曾经风光无限的auto_ptr被抛弃了?
  • 凡事必出有因,我们从一个程序来看:
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main() {
    auto_ptr<string> films[5] =
    {
        auto_ptr<string>(new string("Fowl Balls")),
        auto_ptr<string>(new string("Duck Walks")),
        auto_ptr<string>(new string("Chicken Runs")),
        auto_ptr<string>(new string("Turkey Errors")),
        auto_ptr<string>(new string("Goose Eggs"))
    };
    auto_ptr<string> pwin;
    pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针

    cout << "The nominees for best avian baseballl film are\n";
    for (int i = 0; i < 5; ++i)
    {
        cout << *films[i] << endl;
    }
    cout << "The winner is " << *pwin << endl;
    cin.get();

    return 0;
}
  • 运行该程序我们可以发现,程序会出现运行时崩溃,而编译并不会报错。

这里写图片描述

  • 仔细分析程序不难发现,程序中将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针,输出访问空指针程序当然会崩溃啦。
  • 但如果我们使用另外类型的智能指针,会发现程序并不会崩溃,比如shared_ptr。或者使用内核同样是管理权转移的unique_ptr则会在编译时报错。
  • 那么,我们不难得出结论,不再建议使用auto_ptr的原因有:避免出现潜在的内存崩溃问题。
  • 另外,auto_ptr还存在着release函数只让出内存所有权,并不会释放资源等缺点。
  • 综上,当然要抛弃auto_ptr啦,还有一大片森林等着我们呢,何必要吊死在一棵树上!
  • 一道阅读综述题标准答案:纵使出生就自带光环,但后天不努力提高自身水平的话,最终也不过伤仲永了,泯然众人矣。(突然鸡汤!

unique_ptr /scoped_ptr: 帮auto_ptr填坑的

  • unique_ptr和auto_ptr的内核实质上都是一样的,那就是内存管理权的转移。
  • 但为了填补auto_ptr复制和赋值时编译不报错而造成程序崩溃的问题,unique_ptr简单粗暴地禁止了赋值操作和复制构造。
  • 那,如果当操作者想要把控制权从一个unique_ptr对象转移给另一个unique_ptr对象时,该怎么办呢?
  • 移动构造是可以用来解决这个问题的方法之一。
  • 移动构造是来源于C++11的一个概念,大意为将自己的内容直接转移给另一个对象使用,而自己则失效。
int main() {
    unique_ptr<string> ps1(new string("Hello, unique_ptr!"));
    cout << "ps1 is: " << *ps1 << ", ptr value is: " << ps1.get() << endl;

    // unique_ptr<string> ps2(ps1);// 编译将会出错!因为禁止复制
    // unique_ptr<string> ps2 = ps1;// 编译将会出错!因为禁止赋值
    unique_ptr<string> ps2 = move(ps1);
    cout << "ps1 is: " << ps1.get() << endl;
    cout << "ps2 is: " << *ps2 << ", ptr value is: " << ps2.get() << endl;

    getchar();
    return 0;
}

![Alt text](./unique_ptr.png)

输出结果:

这里写图片描述

  • 可以看到,在执行完move操作之后,ps1内部指针值即指向了NULL。
  • 也就因此,unique_ptr消除了auto_ptr所带来的影响。

shared_ptr:进阶版unique_ptr/scoped_ptr

  • 从上文来看,unique_ptr直接禁止了赋值操作和复制构造,并且能够通过移动构造来完成另一种意义上的复制。
  • 但unique_ptr却并非用户友好型的,那就是unique_ptr只允许最多在一个地方持有对原始对象的指针。
  • 所以,为了解决这个问题,shared_ptr由此应运而生。
  • shared_ptr引入了一个新的功能,那就是引用计数。

    关于RCSP : Reference-counting smart pointer.

    所谓RCSP也是一个智能指针,可以持续追踪共有多少对象指向某笔资源,并在无人指向它的时候,自动删除该资源。

    RSCP提供的行为类似于垃圾回收,不同的是RSCP无法打破环状引用(即循环引用)问题。(例如,两个其实已经没有被使用的对象如果彼此互指,则双方好似都处在“被引用”的状态中。)

  • shared_ptr就是一个RCSP。

  • 当新增一个shared_ ptr对该对象进行管理时,就将该对象的引用计数加一;减少一个shared_ptr对该对象进行管理时,就将该对象的引用计数减一。
  • 如果该对象的引用计数为0的时候,说明没有任何指针对其管理,才调用delete释放其所占的内存。
#include <iostream>
#include <string>
#include <memory>
using namespace std;


int main() {
    shared_ptr<string> ps1(new string("Hello, shared_ptr!"));
    shared_ptr<string> ps3(ps1);    // 允许复制
    shared_ptr<string> ps2 = ps1;   // 允许赋值

    cout << "Count is: " << ps1.use_count() << ", " 
    << ps2.use_count() << ", " << ps3.use_count() << endl;
    cout << "ps1 is: " << *ps1 << ", ptr value is: " << ps1.get() << endl;
    cout << "ps2 is: " << *ps2 << ", ptr value is: " << ps2.get() << endl;
    cout << "ps3 is: " << *ps3 << ", ptr value is: " << ps3.get() << endl;

    shared_ptr<string> ps4 = move(ps1); // 注意ps1在move之后,就“失效”了,什么都是“0”

    cout << "Count is: " << ps1.use_count() << ", " 
    << ps2.use_count() << ", " << ps3.use_count() << ", " << ps4.use_count() << endl;
    cout << "ps1 is: " << ps1.get() << endl;
    cout << "ps4 is: " << *ps4 << ", ptr value is: " << ps4.get() << endl;

    return 0;
}

输出结果:

这里写图片描述

  • 由输出示例可知,输出的指针值均相同,故可知其指向的是同一个对象。

weak_ptr : 帮shared_ptr填了一个小小的缺

  • 所谓的reference counting(引用计数)在非环形的数据结构中,对于防止资源泄露十分有帮助。
  • 但如果两个或多个对象内含shared_ptr并构成了环形,那么这个环形会造成每个对象的引用次数都超过0——即使指向这个环形的所有指针都已被销毁。
  • 所以,weak_ptr因此诞生。
  • weak_ptr的设计使其表现像是“非环形tr1::shared_ptr-based数据结构”中的环形感生指针。
  • 即,weak_ptr并不参与引用计数的计算,但当最后一个指向某对象的tr1::shared_ptr被销毁后,即使还是有weak_ptr指向该对象,该对象还是会被删除。
  • 这种情况下的weak_ptr会被自动标示为无效。

1、再之后就是boost库的发展运用了,这里可以参看boost库的文档进行学习使用,不再赘述。
2、历史发展为博主虚构,不一定准确,不要当真。
3、本文并没有对各智能指针的性能及内核进行详细的剖析,待日后有时间了再来娓娓道来。
4、一点感悟。
  学编程的时候,日常感觉都是很枯燥的,因为其实学习编程的过程就是一个去深入了解计算机的一个过程,从软件也好硬件也好。这其中有一个很严重的问题就是,我慢慢的觉得自己丧失了一部分的情感表达能力,很不开心。
  但后来,采访了一些朋友,发现这个世界上其实没有很多人是真正意义上的开心地活着,总会追究些什么或苛刻于什么。甚至与此同时会觉得,计算机比起人来说,其实是很实诚的。
  可以说是释然了一些吧。
  题外话。我有一个老师常说,自学是最好的因材施教。在这个信息爆炸的时代,想要接触到信息很简单,但想要吸收信息或者分辨信息是很难的。在这个高速化的时代,已经很少人有那种耐性去做一些事情,甚至于学习。可当你从学校走出之后,其实无论如何都是快速折旧的过程,可见终身学习的必要性。所以找到自己喜爱的事情,然后去疯狂地学习是多么的幸福啊。
  很喜欢这个老师,不出意外的话,一个月之后会去给他做一个专题报道。我连标题都想好了,叫程序员也疯狂哈哈哈。有机会的话,也可以在这个博客写写他,很好玩的一个人。
  

Reference

1、书籍:《Effective C++ 第三版》电子工业出版社 条款13
2、博客:http://blog.jobbole.com/108945/ //强推
3、博客:http://www.cnblogs.com/lanxuezaipiao/p/4132096.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值