5

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

 3 years ago
source link: https://blog.popkx.com/C-11%E4%B8%AD%E7%9A%84shared_ptr%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E5%85%A5%E9%97%A8%E7%BA%A7%E4%BB%8B%E7%BB%8D-%E5%8F%8A%E5%85%B6%E7%9B%B8%E5%85%B3%E7%AE%80%E5%8D%95C-%E7%A8%8B%E5%BA%8F%E5%AE%9E%E4%BE%8B/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

“指针问题”

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK