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_ptr
、 unique_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_ptr
。unique_ptr
提供了创建数组对象的特殊方法,当指针离开作用域时,调用delete[]
代替delete
。当创建unique_ptr
时,这一组对象被视作模板参数的部分。这样,程序员就不需要再提供一个指定的析构方法
unique_ptr
提供一个release()
的方法,释放所有权。release
和reset
的区别在于,release
仅仅释放所有权但不释放资源,reset
释放所有权同时也释放资源。