C++11中的shared_ptr智能指针入门级介绍,及其相关简单C++程序实例

“指针问题”

C语言中的指针语法是很多初学者的噩梦,但是由于指针能够便捷的管理内存,后进者C++并没有抛弃指针语法。不过,由于指针过于灵活,很容易为程序带来内存溢出、内存泄漏等问题,即使是经验老道的程序员也不敢说能够完全避免这些问题,所以C++提供了引用语法,以期解决指针带来的问题。

可惜的是,近些年的实践证明C++是离不开指针(这一点我们以后再谈)的,因此早期C语言程序员使用指针面临的问题,C++程序员也不得不考虑。C++的语法比C语言的语法复杂得多,若是不能提供一种缓解“指针问题”的方案,那真的有些说不过去了,所以“智能指针”就被设计出来了。

本文将讨论C++11中的std::shared_ptr智能指针。

C++中的指针的一个典型用法就是管理一段内存。常规做法是通过类似于 malloc() 的内存管理函数,或者new关键字等方法分配一段内存,并且定义一个指针指向这块内存,之后便可通过指针访问这块内存。

不过一般来说,malloc() 或者new关键字分配的内存不会被系统自动回收,这意味着即使分配的内存不再被使用,但若是程序员不主动释放分配的内存,这块内存将永远不能再被别的逻辑使用,这就是所谓的“内存泄漏”。

可能有读者会想,保证 new/malloc() 和 delete/free() 的配对使用不就好了吗?这有什么难的!的确,在简单的项目里比较容易保证C++程序不会发生“内存泄漏”,但若是项目稍稍复杂一些,可能要使用别人提供的接口函数,这时再去确定内存的生命周期就稍显麻烦了,特别是有的同事懒得写文档,而他的接口函数源代码不可得的情况下。

简而言之,C++中类C语言的普通指针管理内存的确有着不方便的地方——程序员必须非常清楚整个架构,才能知道某段已分配的内存是否仍在被使用中,进而确保安全的释放已经不再使用的内存,这并非易事。

std::shared_ptr<> 是什么?

std::shared_ptr<>是C++11标准中的智能指针类,它很聪明,能够知道自己管理的对象是否还有人使用,若是没有人再使用自己管理的对象,就会自动的删除该对象。所以,shared_ptr能够在最大程度上帮助C++程序员避免内存泄漏问题,也能够避免“悬空指针”的出现。

正如shared_ptr的字面含义,“shared”意味着共享,即不同的shared_ptr可以与同一个指针建立联系,其内部通过“引用计数机制”实现自动管理指针。

通常来说,每一个shared_ptr对象在其内部都管理两部分内容:

  1. shared_ptr 对象本身
  2. shared_ptr 对象管理的内容

“引用计数机制”

假设计划使用指针 p 指向一块分配的内存,现在使用C++的智能指针类 shared_ptr 自动管理这块内存。

  • 当 shared_ptr 类实例化一个对象与指针 p 绑定时,其内部的构造函数会将对应指针 p 的计数加 1。
  • 当 shared_ptr 对象完成自己的生命周期,它的析构函数会将指针 p 对应的计数减 1。

引用计数减少到 0,就意味着没有 shared_ptr 对象还与指针 p 管理的内存绑定,也即没有人再使用这块内存了,于是 shared_ptr 的析构函数调用“delete”方法释放这块内存。

创建一个shared_ptr对象

将一个裸指针绑定到 shared_ptr 对象上是简单的,例如:

std::shared_ptr<int> p1(new int());

上面这行C++代码在堆上分配了两块内存,一块用于存储new出来的 int 值,一块用于存储 shared_ptr 对象本身。正如前面讨论的,shared_ptr 对象在其内部使用“引用计数”机制管理“new int()”,这里“计数”的初始值显然是 1,因为暂时只有一个 shared_ptr 对象指向“new int()”上。

C++智能指针类 shared_ptr 提供了成员函数 use_count() 用于检查实际的“计数值”,请参考稍后的实例。

怎样将指针“赋值”给shared_ptr?

因为 shared_ptr 类的构造函数是 Explicit 的,所以像下面这样的C++代码是非法的:

// 非法
std::shared_ptr<int> p1 = new int();

不过,除了前文提到的那样通过shared_ptr构造函数绑定指针,还有一种推荐的方法用于绑定指针:

std::shared_ptr<int> p1 = std::make_shared<int>();

std::make_shared执行了类似于 shared_ptr 构造函数类似的工作:在堆上分配两块内存,一块用于存储 int 值,一块用于存储 shared_ptr 对象本身。

“解绑”

现在我们知道了怎样将普通的裸指针与 shared_ptr 对象绑定,那么怎样才能“解绑”呢?使用 shared_ptr 类提供的 reset() 成员函数即可:

p1.reset();

reset() 函数会将绑定指针的计数减 1,如果计数减小到 0,那么它将删除绑定的指针。reset() 函数也可以接收一个参数,例如:

p1.reset(new int(32));

这种情况下,它将与一个新指针“new int(32)”绑定,因此内部的计数将重新变为 1。

如果想直接解绑 p1 绑定的指针,还可以使用 nullptr,例如:

p1 = nullptr;

shared_ptr 并不是严格意义的“指针”

C++中的 shared_ptr 对象在某种程度上虽然表现的很像传统C语言中的指针,例如可以使用 *->运算符,但是它并不是严格意义上的“指针”,这一点应该始终明白。

实例

下面将使用一个较为完整的智能指针类 shared_ptr 使用实例结束本文:

#include <iostream>
#include  <memory> // 使用shared_ptr需要包含此头文件
int main()
{
    // 使用make_shared创建一个shared_ptr
    std::shared_ptr<int> p1 = std::make_shared<int>();
    *p1 = 78;
    std::cout << "p1 = " << *p1 << std::endl;

    // 打印当前引用计数
    std::cout << "p1 Reference count = " << p1.use_count() << std::endl;

    // 第二个shared_ptr对象绑定到p1
    // 引用计数将会加 1
    std::shared_ptr<int> p2(p1);

    // 打印当前引用计数
    std::cout << "p2 Reference count = " << p2.use_count() << std::endl;
    std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
    // 对比智能指针
    if (p1 == p2) {
        std::cout << "p1 and p2 are pointing to same pointer\n";
    }
    std::cout<<"Reset p1 "<<std::endl;

    p1.reset();

    // 调用reset(),p1将与指针解绑
    // 所以 p1 的引用计数将为 0
    std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;

    // 调用带参数的reset()
    // p1与新指针绑定,引用计数变为 1

    p1.reset(new int(11));

    std::cout << "p1  Reference Count = " << p1.use_count() << std::endl;

    // 通过 nullptr 清空 p1
    p1 = nullptr;

    std::cout << "p1  Reference Count = " << p1.use_count() << std::endl;

    if (!p1) {
        std::cout << "p1 is NULL" << std::endl;
    }
    return 0;
}

编译这段C++代码时,记得添加C++11标准选项,最终得到如下输出:

# g++ t.cpp -std=c++11
# ./a.out 
p1 = 78
p1 Reference count = 1
p2 Reference count = 2
p1 Reference count = 2
p1 and p2 are pointing to same pointer
Reset p1 
p1 Reference Count = 0
p1  Reference Count = 1
p1  Reference Count = 0
p1 is NULL
阅读更多:   C++11智能指针 , C++ , 编程语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif