谈谈智能指针

前言

对智能指针进行学习,并在下一篇博客中实现简单的智能指针。

智能指针简介

C++中使用对内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理;程序员自己管理对内存可以提高程序的效率,但是整体来说对内存的管理是麻烦的;据此引入了智能指针的概念:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。简要来说智能指针能防止内存泄漏和非法指针引用。
操作的方法:在构造函数时,将类外需要管理的指针传进去,可以给个空的缺省值,然后重载 “->”“*”“=”… 等符号,然后在析构函数时,释放这个指针空间,形成对这个指针的智能的管理。

理解智能指针可以从下面三个层次:
1.智能指针利用了一种资源获取即初始化的技术对指针进行封装,这使得智能指针实质上是一个对象,行为表现的却像一个指针。
2.智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也必须注意,否则多次释放同一个指针会造成程序崩溃。
3.智能指针可以把值语义转换成引用(对象)语义,关于语义,举个例子:

Animal a=new Animal();
Animal b=a;

在Java中只剩成了一个对象,a和b仅仅是对对象的引用;但C++中

Animal a;
Animal b=a;

这里却生成了两个对象,关于提到的值语义可以参考陈硕老师的这一篇博客
值语义指的就是对象的拷贝和原对象无关,就像拷贝int一样,C++的内置类型(bool/int/double/char)都是值语义,标准库里的 complex<> 、pair<>、vector<>、map<>、string 等等类型也都是值语义,拷贝之后就与原对象脱离关系。
引用(对象)语义指的是面向对象意义下的对象,对象拷贝是禁止的,因为拷贝一个对象是没有意义的。

4种智能指针及其使用

智能指针在C++11版本之后提供,包含在头文件中,有shared_ptr,unique_ptr,weak_ptr与auto_ptr.

auto_ptr

通常在"获取一些资源、执行一些动作、释放所获取的资源"模式下执行,举一个例子:

void fun()
{
    ClassA* ptr=new ClassA;
    ....
    delete ptr;
}|

常常遇到的麻烦是,我们经常忘掉delete动作,而且如果程序发生异常在运行中出现,函数将立刻退离,从而忽略了函数微端的delete语句;这样做的结果就是内存遗失和资源遗失。我们如果想解决这样的问题就需要用到异常处理try{}catch{},变成:

void fun()
{
    ClassA* ptr=new ClassA;
    ....
    try{
    ...
    }
    catch(...){
    delete ptr;
    throw;
    }
    delete ptr;
}|

为了解决异常发生时处理对象的删除工作,代码就显得很赘余。所以我们需要使用智能指针,无论在何种情况下,只要自己被摧毁就一定会连带释放器所有资源,这就是auto_ptr。
auto_ptr是"所指向对象"的拥有者,当拥有者被摧毁时该对象也会遭到摧毁,所以之前的代码可以写成:

#include<memory>
void fun()
{
    std::auto_ptr<ClassA>ptr(new ClassA);
    .....
}

这里注意一下,auto_ptr<>不允许使用一般指针惯用的赋值初始化方式,auto_ptr的赋值只能用另一个auto_ptr来操作:

std:auto_ptr<ClassA> ptr1(new ClassA);     //OK
std:auto_ptr<ClassA> ptr2= = new ClassA;  //Error

同时,auto_ptr所界定的是一种严格的拥有权观念,只能拥有一个对象,相对的绝对不应该出现多个auto_ptrs同时拥有一个对象的情况。但是如果我们用同一个对象作为初值,将两个auto_ptr初始化,不就会发生错误的情况了么?换一种问法,auto_ptr的复制构造函数和assignment操作符是如何运作的呢?这就涉及到一个概念"拥有权的转移":

std::auto_ptr<ClassA>ptr1(new ClassA);
//拥有权转移
std::auto_ptr<ClassA>ptr2(ptr1);

在第一个语句中,ptr1拥有了new出来的对象,第二个语句宏将ptr1的拥有权转交给ptr2,此后ptr2就拥有了呢个new出来的对象,ptr1不再拥有。auto_ptr的语义本身就包含了拥有权,所以如果无意转交拥有权,就不要在参数列中使用auto_ptr,也不要用它作为返回值。
但我们可以运用constant reference来向函数传递拥有权:

const std::auto_ptr<int> p(new int);
*p=42;    //change value to which p refers
bad_print(p);
*p=8;    //OK

关键词const并非意味着不能更改auto_ptr所拥有的对象,而是意味着不能更改auto_ptr的拥有权。
auto_ptrs的一些要点:
1.auto_ptrs之间不能共享拥有权
2.不存在针对array而设计的auto_ptrs
3.auto_ptrs不满足STL容器对元素的要求
但也正是由于这些缺陷,才使得auto_ptr这个智能指针只有着概念上的作用而很少在实际上使用。

shared_ptr

shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

  • [ ]初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr p4 = new int(1);的写法是错误的
  • [ ]拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
  • [ ]get函数获取原始指针
  • [ ]注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
  • [ ]注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。

unique_ptr

unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

#include <iostream>
#include <memory>

int main() {
    {
        std::unique_ptr<int> uptr(new int(10));  //绑定动态对象
        //std::unique_ptr<int> uptr2 = uptr;  //不能賦值
        //std::unique_ptr<int> uptr2(uptr);  //不能拷貝
        std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權
        uptr2.release(); //释放所有权
    }
    //超過uptr的作用域,內存釋放
}

weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()true的时候,lock()函数将返回一个存储空指针的shared_ptr。

int main() {
    {
        std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
        std::cout << sh_ptr.use_count() << std::endl;

        std::weak_ptr<int> wp(sh_ptr);
        std::cout << wp.use_count() << std::endl;

        if(!wp.expired()){
            std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
            *sh_ptr = 100;
            std::cout << wp.use_count() << std::endl;
        }
    }
    //delete memory
}
原文地址:https://www.cnblogs.com/yunlambert/p/9518045.html