Loading

C++11智能指针

程序使用 new 从堆(自由存储区)分配内存,在不需要时,应使用 delete 将其释放。这个内存管理过程由程序员控制,内存泄露是困扰C/C++程序员的一大难题。,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

在C++11中摒弃了auto_ptr(在 C++ 17 中被移除),新增三种智能指针:

  • shared_ptr :通过指针保持对象共享所有权的智能指针;
  • weak_ptr:到 std::shared_ptr 所管理对象的弱引用,在访问所引用的对象前必须先转换为 std::shared_ptr ;
  • unique_ptr: 拥有独有对象所有权语义的智能指针。

显示调用

shared_ptrunique_ptr 都有一个 explicit 构造函数,因此不能自动将指针转换为智能指针对象,必须显式调用

4.8.2

explicit shared_ptr(_Tp1* __p)
        : __shared_ptr<_Tp>(__p) { }
        
explicit unique_ptr(pointer __p) noexcept
      : _M_t(__p, deleter_type())
      { static_assert(!is_pointer<deleter_type>::value,
		      "constructed with null function pointer deleter"); }

shared_ptr

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。shared_ptr的目标非常简单:多个指针可以同时指向一个对象。下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过operator=reset()]赋值为另一指针时。
int *p_int = new int;
shared_ptr<int> sptr1(p);			// OK
shared_ptr<int> sptr2 = sptr1;		// OK 引用计数+1
shared_ptr<int> sptr3 = p_int;		// Error!!!
shared_ptr<int> sptr1 = make_shared<int>(100); // OK 使用 make_shared

shared_ptr析构

shared_ptr 默认调用 delete 释放关联的资源。如果用户采用一个不一样的析构策略时,他可以自由指定构造这个 shared_ptr 的策略。如下:当离开作用域时,默认的析构函数调用 delete 释放资源。实际上,我们应该调用 delete[] 来销毁这个数组。

class Test {
private:
	int m_a;
};

void main(){
	shared_ptr<Test> sptr1(new Test[5]);  // 默认使用 delete 析构
}

用户可以通过调用一个函数,例如一个 lamda 表达式,来指定一个通用的释放步骤。

shared_ptr<Test> sptr1(new Test[5], [ ](Test* p) { delete[] p; });

常用接口

  • shared_ptr 也提供解引用操作符 *->

  • std::make_shared :创建管理一个新对象的共享指针

  • get() :获取shared_ptr绑定的资源。

  • reset() :释放关联内存块的所有权,如果是最后一个指向该资源的shared_ptr,就释放这块内存。

  • unique :判断是否是唯一指向当前内存的shared_ptr

  • operator bool :判断当前的shared_ptr是否指向一个内存块,可以用if 表达式判断。

原始指针初始化shared_ptr

不要用一个原始指针初始化多个 shared_ptr,否则会造成二次释放同一内存。

void main() {
    int* ptr = new int;
    shared_ptr<int> p1(ptr);  // 引用计数 1
    shared_ptr<int> p2(ptr);  // 引用计数 1   p1、p2两者不知对方存在
}

如上,直接使用原始指针初始化多个 shared_ptr ,使用时,智能指针相互间不知道彼此的存在。p1、p2存储在栈上,程序结束时,首先 p2 死亡时引用计数为 0 ,释放掉 ptr 。接着 p1 死亡,p1 记录的引用计数也为 0 。再次对对象进行释放,Crash !!!

循环引用

引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。

class Parent {
public:
    ~Parent() { std::cout << "Parent destroying..." << std::endl; }

public:
    shared_ptr<Child> m_child;
};

class Child {
public:
    ~Child() { std::cout << "Child destroying..." << std::endl; }

public:
    shared_ptr<Parent> m_parent;
}

void main() {
	shared_ptr<Parent> p_parent(new Parent);
	shared_ptr<Child> p_child(new Child);
    p_child->m_parent = p_parent;
    p_parent->m_child = p_child;
}

两个对象的引用计数都是 2 。当 p_child 离开作用域时,子类对象引用计数 -1 (值为1)。当 p_parent 离开作用域时,父类对象引用计数 -1 (值为1)。两个智能指针生命期都结束了,但是对象内存却都没得到释放!!!

解决循环引用

  • 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
  • 当parent的生存期超过child的生存期的时候,child改为使用一个普通指针指向parent。
  • 使用弱引用的智能指针打破这种循环引用。

weak_ptr

为了解决循环引用C++提供了另外一种智能指针:weak_ptr

std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr

强引用:被引用的对象活着,这个引用就存在。只要有一个强引用存在,这个对象就不能被释放。

弱引用:弱引用不修改对象的引用计数,意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

class Parent {
public:
    ~Parent() { std::cout << "Parent destroying..." << std::endl; }

public:
    weak_ptr<Child> m_child;
};

class Child {
public:
    ~Child() { std::cout << "Child destroying..." << std::endl; }

public:
    weak_ptr<Parent> m_parent;
}

unique_ptr

std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。

在下列两者之一发生时用关联的删除器释放对象:

  • 销毁了管理的 unique_ptr 对象。
  • 通过 operator=reset() 赋值另一指针给管理的 unique_ptr 对象。

unique_ptr的创建方法和shared_ptr一样,除非创建一个指向数组类型的unique_ptrunique_ptr提供了创建数组对象的特殊方法,当指针离开作用域时,调用delete[]代替delete。当创建unique_ptr时,这一组对象被视作模板参数的部分。这样,程序员就不需要再提供一个指定的析构方法

unique_ptr提供一个release()的方法,释放所有权。releasereset的区别在于,release仅仅释放所有权但不释放资源,reset释放所有权同时也释放资源。

posted @ 2021-01-25 14:17  JakeLin  阅读(114)  评论(0编辑  收藏  举报