美文网首页
谈智能指针

谈智能指针

作者: 404Not_Found | 来源:发表于2021-12-12 14:19 被阅读0次
  • 作者: 雪山肥鱼
  • 时间:20211211 16:25
  • 目的: 复习智能指针

内容来源: <<深入应用C++11>>
第4章 使用C++11解决内存泄漏的问题

引言

只能指针是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保在离开指针所在作用域,自动的销毁动态分配的对象,放置内存泄漏。它的一种通用实现技术是使用引用计数。每使用一次,内部引用计数+1,每析构一次,内部引用计数-1,减为0时, 删除所指向的堆内存。

C++11 实现了3种智能指针:

  • std::shared_ptr
  • std::uniq_ptr
  • std::weak_ptr
    引入的头文件 <memory>

1. shared_ptr 共享的智能指针

shared_ptr 使用引用计数,每一个shared_ptr 拷贝都指向相同的内存。直到最后一个shared_ptr 析构的时候,内存才会被释放。计数归0.

1.1 shared_ptr的基本用法

  1. 初始化
//shared_ptr 初始化方式
shared_ptr<int> p (new int(1));
shared_ptr<int> p2 = p;

//未初始化的智能指针可以通过 reset 初始化
shared_ptr<int> ptr;
ptr.reset(new int(1)); //reset 是的 引用计数-1

if(ptr) {
  cout<<"ptr  is not null"<<endl;
}

auto t = std::make_shared<int>(10);

不能将一个原始指针,直接赋值给一个智能指针

shared_ptr<int> p = new int (1); //编译错误,不允许直接赋值

智能指针的用法和普通指针用法相似,只不过,不用管理自己分配的内存。
shared_ptr 不能通过直接将原始指针赋值来初始化,需要通过构造函数和辅助方法来初始化

由代码可知,智能指针可以通过重载的bool 来判断是否为空

  1. 获取原始指针
shared_ptr<int> ptr(new int(1));
int * p = ptr.get();
  1. 指定删除器
void DeleteIntPtr(int *p) {
  delete p;
}

shared_ptr<int> p(new int, DeleteIntPtr);

//删除器也可以用lambda
shared_ptr<int> p(new int, [ ](int * p){delete p;});

但是 shared_ptr 管理动态数组的时候,需要指定删除器, 也就是说并不完美的支持 数组

//方法1:
shared_ptr<int> p (new int[10], [](int*p){delete [ ] p;});

//方法2:
shared_ptr<int> p (new int [10], default_delete<int[ ]>);

//方法3 封装 make_shared_array 方法,让 shared_ptr支持数组
template<typename T>
shared_ptr<T> make_shared_array(size_t size) {
  return shared_ptr<T>(new T[size], default_delete<T[]>());
}

测试代码如下:

shared_ptr<int> p = make_shared_array<int>(10);
shared_ptr<char> p = make_shared_array<char>(10);

shared_ptr 应该注意的问题

  1. 不要用一个原始指针初始化多个shared_ptr
int * ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error
  1. 不要在函数实参种创建shared_ptr
function(shared_ptr<int>(new int), g( )); //有缺陷

因为C++ 的函数参数计算顺序在不同的编译器,不同的调用约定下,调用顺序不同,一般从右到左,也可能从左到右。
如果先new int, 然后调g( ), 如果g( ) 发生异常,而 shared_ptr<int> 还没创建,则int 内存泄漏。
正确写法:

shared_ptr<int> p (new int());
f(p, g() );
  1. 通过shared_from_this() 返回 this 指针,不要将this 指针作为shared_ptr 返回出来,因为 this 本质上是一个裸指针,非常有可能导致重复析构。
struct A {
  shared_ptr<A> GetSelf() {
    return shared_ptr<A>(this);
  }
};

int main(int argc, char **argv) {
  shared_ptr<A> sp1(new A);
  shared_ptr<A> sp2 = sp1->GetSelf();
  return 0;
}

用同一个 this 指针 构造了 两个智能指针 sp1, sp2, 而两者之间又毫无瓜葛。所以 离开mian 函数后 会各自析构,导致重复析构的错误。

正确返回this 的 shared_ptr 做法是: 让目标类通过派生 std::enable_shared_from_this<T>类,然后使用基类成员 shared_from_this 返回this 的shared_ptr。

class A: public std::enable_shared_from_this<A> {
    shared_ptr<A> GetSlef() {
        return shared_from_this();
    }
}

shared_ptr<A> spy(new A);
shared_ptr<A> p = spy->GetSelf();

手动的让两者建立联系

  1. 避免循环引用。智能指针的最大一个陷阱就是循环引用,循环引用会导致内存泄漏。
struct A;
struct B;

struct A {
  shared_ptr<B> bptr;
  ~A() {
      cout<<"A is deleted!"<<endl;
  }
};

struct B {
  shared_ptr<A> aptr;
  ~B() {
      cout<<"B is deleted!"<<endl;
  }
}

void TestPtr() {
  {
      shared_ptr<A> ap(new A);
      shared_ptr<B> bp(new B);
      ap->bptr = bp;
      bp->aptr = ap;
  }
}

循环引用导致 ap, bp 的计数均为 2, 脱离作用域后,为1,不会减少到0.导致两个指针不会被析构,插死你哼内存泄漏。解决办法为后续的weak_ptr.

2. unique_ptr 独占的智能指针

unique_ptr 是一个独占的智能指针,不允许其他智能指针共享其内部的指针。

unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = myPtr; //错误 不可复制

但可以通过std::move, 转移其他unique_ptr,这样它本身就不再拥有原来指针的所有权了。

unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = std::move(myPtr);

//错误
unique_ptr<T> ptr = myPtr;

unique_ptr 不像shared_ptr 可以通过make_shared 方法来创建智能指针。至少c++11,没有,c++14 有类似的make_unique来创建unique_ptr.

template<class T, class... Args> inline
typename enable_if<!is_array<T>::value, unique_ptr<T>>::type
make_unique(Args&&... args)
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template<class T> inline
typename enable_if<is_array<T>::value && extent<T>::value == 0, unique_ptr<T>>::type
make_unique(size_t size)
{
    typedef typename remove_extent<T>::type U;
    return unique_ptr<T>(new U[size]());
}

template<class T, class... Args>
typename enable_if<extent<T>::value !=0, void>::type
make_unique(Args&&...) = delete;
  • 不同点 1
  1. 不是数组,直接创建 unique_ptr
  2. 是数组
    2.1 先判断是否为定长数组,如果是定长数组则编译不通过
    因为不能这样调用make_unique<T[10]>(10), 应该 make_unique<T[ ]>(10)
    2.2 若非定长数组,则获取数组种的元素类型,再根据入参size 创建动态数组的unique_ptr
  • 不同点2
    unique_ptr 和 shared_ptr 相比,unique_ptr 除了独占性这个特点外,还可以指向一个数组。
std::unique_ptr<int [ ]> ptr(new int[10]);
ptr[9] = 9;

注意:
shared_ptr<int[ ]>ptr(new int[10]) 是不合法的

不同点3

删除器指定方式不同

std::shared_ptr<int> ptr(new int(1), [ ](int *p){delete p;}); //正确
std::unique_ptr<int> ptr(new int(1), [ ](int* p){delete p;}); //错误

unique_ptr 指定删除器的时候,需要确定删除器的类型,所以不能像shared_ptr 那样直接指定删除器

std::unique_ptr<int, void(*)(int *)>ptr(new int(1), [ ](int* p){delete p;});

带了 lambda ,如果没有捕获变量,是正确的,如果捕获变量,则会编译报错。

std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [ &](int* p){delete p;});

lambda在没有捕获变量的情况下是可以转换为函数指针的,一旦捕获了就无法转换了,因为你在类型里指定的是函数指针
如果希望删除器 支持 lambda,

std::unique_str<int, std::function<void(int*)>>ptr(new int(1), [&](int* p){delete p;});

换句话,lambda如果捕获了变量,则转换为 function 对象

当然也可以自定义删除器 - 仿函数

#include <memory>
#include <functional>
using namespace std;

struct MyDeleter
{
  void operator()(int*p)
  {
    cout<<"delete"<<endl;
    delete p;
  }
};

int main() {
  std::unique_ptr<int, MyDeleter> p (new int(1));
  return 0;
}

非常明显,如果希望只有一个智能指针管理资源或管理数组,就用unique_ptr, 如果希望多个智能指针管理同一个资源,就用 shared_ptr

3. weak_ptr 弱引用的智能指针

weak_ptr 可以视为 是 shared_ptr 的辅助,并不管理shared_ptr内部内容,主要是为了监视 shared_ptr的生命周期。
weak_ptr 没有重载 * 和 -> ,不共享指针,不能操作资源,主要是通过shared_ptr 获得资源的监控权。不增加引用计数,析构也不会减少引用计数。

weak_ptr 可以用来返回 this 指针和 解决循环引用的问题

3.1 weak_ptr 基本用法

  1. 通过 use_count()方法获得当前资源的引用计数:
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);

cout<<wp.use_count()<<endl; //1
  1. 通过 expired() 方法来判断所观测的资源是否已经被释放
shared_ptr<int > sp(new int(10));
weak_ptr<int> wp(sp);

if(wp.expired())
  std::cout<<"invalid weakptr, original ptr has been released"<<std::endl;
else
  std::cout<< "valid weakptr"<<endl;
  1. 通过lock 方法获取所监视的 shared_ptr
std::weak_ptr<int> gw;

void f() {
  if(gw.expired()) {
    std::cout<<"gw is expired\n";
  }
  else {
    auto spt = gw.lock();
    std::cout<<*spt<<std::endl;
  }
}

int main(int argc ,char **argv) {
  {
    auto sp = std::make_shared<int>(42);
    gw = sp;
    f();
  }
  f();
}

//output
42
gw is expired

3.2 weak_ptr 返回 this 指针

不能直接将 this 指针 返回为 shared_ptr,需要通过派生 std::enable_shared_from_this类,并通过其方法 shared_from_this 返回 智能指针。

原因:
std::enable_shared_from_this类中 有一个 weak_ptr,这个weak_ptr 用来观测 this指针。
调用shared_from_this() 方法时,会调用内部 weak_ptr的lock()方法,将所观测的shared_ptr 返回。

一个是直接返回 this, 一个是 返回shared_ptr,就是两者的不同。

struct A : public std::enable_shared_from_this<A> {
  std::shared_ptr<A> GetSelf() {
    return shared_from_this();
  }

  ~A() {
     cout << "A is deleted"<<endl;
  }
}

std::shared_ptr<A> spy(new A);
std::shared_ptr<A> p = spy->GetSelf();

以上的操作都是安全的。shared_from_this(),调用的是内部的weak_ptr的 lock(),返回的智能指针。在离开作用域之后,spy的引用计数为0. A对象会被析构,不会造成重复析构的问题。
注意,获取自身智能指针的函数,仅在shared_ptr<T> 的构造函数执行完成后才能使用,因为 enbale_shared_from_this 内部 的 weak_ptr 只有通过shared_ptr 才能构造。因为存在继承的关系嘛

3.3 weak_ptr解决循环引用问题

问题:

struct A;
struct B;

struct A {
  shared_ptr<B> bptr;
  ~A() {
    cout<<"A is deleted!"<<endl;
  }
};

struct B {
  shared_ptr<A> aptr;
  ~B() {
    cout<<"B is deleted!"<<endl;
  };

void TestPtr() {
  {
    shared_ptr<A> ap(new A);
    shared_ptr<B> bp(new B);
    ap->bptr = bp;
    bp->aptr = ap;
  }
}
}

上述代码中 ,由于循环引用 ap, bp 的引用计数分别为2. 俩开作用域后,都只减1. 会造成内存泄漏。
结局方案:只要将A 或 B 的任意一个成员改为 weak_ptr即可。

struct A;
struct B;

struct A {
  shared_ptr<B> bptr;
  ~A() {
    cout<<"A is deleted!"<<endl;
  }
};

struct B {
  weak_ptr<A> aptr;//修正为weak_ptr
  ~B() {
    cout<<"B is deleted!"<<endl;
  }
};

void TestPtr() {
  {
    shared_ptr<A> ap(new A);
    shared_ptr<B> bp (new B);
    ap-> bptr = bp;
    bp->aptr = ap;
  }
}

在对B 的成员赋值时,即执行bp->aptr = ap 时,由于 aptr 是 weakptr,所以 ap 的引用计数为1.
所以 ap 析构干净后,为0, 那么ap 中的 bp 析构一次,bp 自己再析构一次。则 不会发生内存泄漏。

4. 通过智能指针管理第三方库的分配内存

用智能指针来管理第三方库分配的内存是比较稳妥的。
通常下面的代码是存在风险的

void *p = GetHandle() -> Create();
// do sth...
Gethandle() -> Release(p);

总会有人忘记 Release.

void * p = GetHandle()->Create();
shared_ptr<void> sp (p, [this](void * p) {
  GetHandle() -> Release(p); //为什么会挂上this,我不太理解  
})

上述代码,有些繁琐,因为每次 create() 后都要写这么一长串。 可以提炼出一个公共函数

shared_ptr<void> Guard(void * p) {
  return shared_ptr<void> sp( p, [this](void * p) {Gethandle() -> Release(p);
  }
}

void *p = GetHandle() -> Create();
auto sp = Guard(p);
//do sth...

上述代码虽然做了简化,但是仍然有隐患,如果没用变量去接 Guard(p) ,该行结束,则p 会被当作临时对象释放。
会很容易造成野指针的出现。

所以用宏是最安全的

#define GUARD(p) std::shared_ptr<void> p##p(p, [ ](void *p) {GetHandle()->Release(p) });

void*p = GetHandle()->Create();
GUARD(p);

会用p 初始化一个 新的 shared_ptr<int> pp,所以是较为安全的
通过宏定义的方式可以保证不会忘记智能指针没有赋值,即方便又安全。

4 总结

智能指针是为没有GC的语言解决可能的内存泄漏问题。但是实际应用中,智能指针有一些需要注意的地方,好在这些问题都可以解决。

  • shared_ptr 和 uniq_ptr 使用时 如何选择
  • weak_ptr 是 shared_ptr 的 助手。只是监视shared_ptr 而已。解决shared_ptr 的循环引用和this 指针问题。

相关文章

  • 目录

    智能指针(1) 智能指针(2) 智能指针(3) 智能指针之使用 容器 - vector(1) 容器 - vecto...

  • 智能指针到Android引用计数

    智能指针 LightRefBase RefBaseStrongPointerWeakPointer 智能指针 这是...

  • C++面试重点再梳理

    智能指针 请讲一下智能指针原理,并实现一个简单的智能指针 智能指针其实不是一个指针。它是一个用来帮助我们管理指针的...

  • C++研发工程师笔试题/面试题(1-10)

    1. (1) 简述智能指针的原理;(2)c++中常用的智能指针有哪些?(3)实现一个简单的智能指针。 简述智能指针...

  • 第十六章 string类和标准模板库(2)智能指针模板类

    (二)智能指针模板类 智能指针是行为类似指针的类对象,但这种对象还有其他便于管理内存的功能。 1.使用智能指针 (...

  • Rust for cpp devs - 智能指针

    与 cpp 类似,Rust 也有智能指针。Rust 中的智能指针与引用最大的不同是,智能指针 own 内存,而引用...

  • C++ 引用计数技术及智能指针的简单实现

    1.智能指针是什么 简单来说,智能指针是一个类,它对普通指针进行封装,使智能指针类对象具有普通指针类型一样的操作。...

  • 智能指针

    1. 什么是智能指针? 智能指针是行为类似于指针的类对象,但这种对象还有其他功能。 2. 为什么设计智能指针? 引...

  • chrome中智能指针使用

    chrom中智能指针的概述和作用 chrome中智能指针的用法和总结 包含如下四种智能指针:scoped_ptr ...

  • c++智能指针用法

    智能指针是什么 智能指针是c++中有四个智能指针:auto_ptr、shared_ptr、weak_ptr、uni...

网友评论

      本文标题:谈智能指针

      本文链接:https://www.haomeiwen.com/subject/zzrrfrtx.html