[C++]智能指针的原理与使用

1、智能指针的原理及作用 

       C++程序中不仅包含静态内存和栈内存,还有一个内存池,内存池中的内存被称为自由空间或者堆。程序通常使用堆来存储动态分配的对象(程序运行时分配的对象),当动态对象不再被使用时,代码必须显式的将它们销毁。动态内存的管理是通过运算符new和delete完成的。

new运算符:在动态内存中为对象分配一块空间并返回一个指向该对象的指针。

int i;

int *p0 = &i;

int *p1 = new int; //指向一个动态分配的、未初始化的无名对象

int *p2 = new int(2); //*p2初始化值为2

int *p3 = new int[1000]; //申请1000个单位的内存空间

delete运算符:指向一个动态对象的指针,销毁对象并释放与其相关联的内存。在delete之后,指针变成了悬空指针(指向一块曾经保存数据对象但现在已经无效的内存地址)。想要避免悬空指针,需要在delete之后将nullptr赋值给指针变量,这样就清楚的指出指针不指向任何对象。

delete p0; //error, p0指针不是用new动态申请的

delete p1;
p1 = nullptr;

delete p2;
p2 = nullptr;

delete[] p3; //在用new申请时用了[],所以在delete时也要用[]

使用new和delete运算符进行动态内存的管理虽然可以提高程序的效率,但是也非常容易出问题。

      (1)忘记释放内存,造成内存泄漏

      (2)在尚有指针引用内存的情况下就将其释放,产生引用非法内存的指针

      (3)程序发生异常后进入catch忘记释放内存以及多次释放同一块内存,造成内存泄漏

       为了让动态内存的使用更加安全、简便,C++引入了智能指针的概念。什么是智能指针?智能指针是借用RAII技术对普通指针进行封装,其实质是一个对象,行为表现为一个指针,也就是智能的管理动态内存的释放。那RAII技术具体又是什么呢?RAII技术(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。使用类来封装资源的分配和初始化,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理,可以保证正确的初始化和资源释放。     

2、智能指针的分类和使用

      C++11版本之后提供的智能指针包含在头文件中,分别是auto_ptr、shared_ptr、unique_ptr、weak_ptr。其中auto_ptr已被弃用,所以这里不再赘述。

2.1  shared_ptr

      shared_ptr是一种强引用指针,允许多个指针指向相同的对象,是一个标准的共享所有权的智能指针。每个shared_ptr都会有一个计数器与之相关联,通常称其为引用计数。下面,我们简单介绍下shared_ptr的用法:

(1)初始化

    可以使用构造函数初始化(指定类型,传入指针即可),也可以使用make_shared函数初始化(最安全的方法,推荐使用)

std::shared_ptr sp; //空shared_ptr,可以指向类型为T的对象

std::shared_ptr sp(new int(5)); //指定类型,传入指针通过构造函数初始化

std::shared_ptr sp = std::make_shared(5); //使用make_shared函数初始化

//智能指针是一个模板类,不能将一个原始指针直接赋值给一个智能指针,因为一个是类,一个是指针
std::shared_ptr sp = new int(1); //error

//shared_ptr不能直接支持动态数组,需要显示指定删除器

std::shared_ptr sp(new int[10], [](int* p){delete[] p;}); //指定delete[]

std::shared_ptr sp(new int[10], std::default_delete()); //指定std::default_delete

(2)拷贝和赋值

       当拷贝一个shared_ptr时,内部的引用计数加1;当给shared_ptr赋值或shared_ptr被销毁时,内部的引用计数减1;当引用计数减为0时将会自动释放自己所管理的对象。

std::shared_ptr sp = std::make_shared(5); //sp指向的int对象只有一个引用者,引用计数为1
std::shared_ptr spCopy(sp); //spCopy是sp的拷贝,此操作会递增sp指向对象的引用计数

std::shared_ptr sp1 = std::make_shared(6);
sp = sp1; //给sp赋值,使它指向另一个地址,此操作会递增sp1指向对象的引用计数,递减sp原来指向对象的引用计数

(3)自动释放所管理的对象以及相关联的内存

      shared_ptr是通过其析构函数来完成自动释放的工作。shared_ptr的析构函数会递减其所指向对象的引用计数,当引用计数变为0时,析构函数就会销毁对象并释放其所占用的内存。注意:若将shared_ptr存放于一个容器中,而后只使用其中部分元素,不再需要全部元素时,千万记得用erase删除不在需要的那些元素。

(4)和普通指针的混合使用

 

 

2.2  unique_ptr

       顾名思义,unique_ptr唯一拥有其所指的对象,在同一时刻只能有一个unique_ptr指向给定对象,因此unique_ptr不支持普通的拷贝和赋值操作。下面,我们简单介绍下unique_ptr的用法:

(1)初始化

       unique_ptr不像shared_ptr一样拥有make_shared函数来创建一个shared_ptr实例。因此我们需要将一个new操作符返回的指针传递给unique_ptr的构造函数来完成初始化。unique_ptr提供了对动态数组的支持,指定删除器是一个可选项。

std::unique_ptr up; //空unique_ptr,可以指向类型为T的对象,up会使用delete来释放它的指针

std::unique_ptr up(new int(5)); //绑定动态对象

std::unique_ptr up(new int[5]); //可选择是否指定删除器

(2)拷贝和赋值

      unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作;但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr(使用std::move函数,也可以调用release或reset)。

std::move:获取一个绑定到左值上的右值引用

u.release:u放弃对指针的控制权,返回指针,并将u置为空

u.reset:释放u指向的对象

u.reset(q):如果提供了内置指针q,令u指向这个对象,否则将u置为空

std::unique_ptr up(new int(5));

std::unique_ptr upCopy(up); //error,不能拷贝

std::unique_ptr upAssign = up; //error,不能赋值

std::unique_ptr upMove = std::move(up); //转移所有权

std::cout << *up << std::endl; //error,up经过std::move后为空

std::unique_ptr up1(new int(5));

std::unique_ptr up2(up1.release()); //up2被初始化为up1原来保存的指针,且up1置为空

std::unique_ptr up3(new int(6));

up2.reset(up3.release()); //reset释放了up2原来指向的内存,指向up3原来保存的指针,且将up3置为空

(3)使用场景

      unique_ptr适用范围比较广泛,它可返回函数内动态申请资源的所有权;可在容器中保存指针;支持动态数组的管理。

std::unique_ptr CloneUniquePtr(int p)
{
    std::unique_ptr up(new int(p));
    return up; //返回unique_ptr
}

int main()
{
    //返回函数内动态申请资源的所有权,函数结束后,自动释放资源
    int p = 5;
    std::unique_ptr up = CloneUniquePtr(p);
    std::cout << *up << std::endl;

    //在容器中保存指针
    std::vector> vecIntPtr;
    std::unique_ptr up1(new int(5));
    vecIntPtr.push_back(std::move(up1)); //注意要使用std::move

    //支持动态数组
    std::unique_ptr up2(new int[5] {1, 2, 3, 4, 5});
    up2[0] = 0; //重载了operation[]
}

2.3  weak_ptr

       weak_ptr是一种弱引用指针,它是伴随shared_ptr而来的,不具有普通指针的行为。它主要是解决了shared_ptr引用计数的问题:在循环引用时会导致内存泄漏的问题。

(1)初始化

      weak_ptr指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。

std::weak_ptr wp; //空weak_ptr,可以指向类型为T的对象

std::weak_ptr wp(new int(5)); //使用weak_ptr对象构造

std::shared_ptr sp = std::make_shared(6);
std::weak_ptr wp(sp); // 使用shared_ptr对象构造

(2)成员函数

wp.use_count():与该wp共享的shared_ptr的数量

wp.expired():如果wp.use_count() == 0,返回true,否则返回false

lock():如果wp.expired()为true,返回一个空shared_ptr,否则返回一个指向wp对象的shared_ptr

成员函数的使用如下:

std::shared_ptr sp = std::make_shared(5);
std::weak_ptr wp(sp);
std::cout << wp.use_count() << std::endl;

if(!wp.expired())
{
    std::shared_ptr sp2 = wp.lock();
    std::cout << *sp2 << std::endl;
}

 

你可能感兴趣的:(C++开发)