C++智能指针学习
[toc]
智能指针内存管理要解决的根本问题是:一个堆对象,在被多个对象引用时,如何释放资源的问题。
shared_ptr
shared_ptr智能指针内存管理的思路
shared_ptr智能指针的解决思路:最后一个引用它的对象被释放时,释放这段内存。
实现方法:对 被管理的资源 进行计数。当一个sharedptr对象要共享这个资源的时候,该资源的引用计数加1,当该对象生命周期结束了,再把该引用计数减1。这样,当最后一个引用它的对象被释放的时候,资源的引用计数减少到0,此时释放该资源。
智能指针的使用
智能指针的内部实现
- 智能指针最终的实现是 两个指针成员:一个指向数据成员,一个指向计数器成员
- 智能指针里的计数器 维护的是一个指针,指向的 实际内存 在堆上,不是栈上的
智能指针拷贝构造的原理
reset
reset的功能是:释放对象,默认情况下置空指针。能够被安全地多次调用。
p.reset()
p.reset(q)
p.reset(q, d)
如果p是唯一指向其对象的shard_ptr, reset会释放对象。
如果传递了可选参数内指针q,则会令p指向p;
否则将p置空。
如果还传递了参数d,则将会调用d而不是delete来释放q。
释放q的时机不是发生在reset时,而是q在运行到需要释放的时候。这里说明的是 reset会传递给它一个单独的delete函数
智能指针赋值nullptr
表现和reset的行为一致,会递减对象的计数器,如果减为0,则调用析构,释放对象。
class C {
public:
~C() {
cout << "C dtor" << endl;
}
};
using namespace cycle_ref;
shared_ptr<cycle_ref::C> sp(new cycle_ref::C());
sp = nullptr;
cout << "before exit" << endl;
运行结果:在赋值nullptr时,调用了析构。
C dtor
before exit
weak_ptr
概念
是一种不控制所指向对象生命周期的智能指针,它指向一个shared_ptr管理的对象。
weak_ptr绑定到shared_ptr时,不会改变对象的引用计数。
当shared_ptr被销毁时,指向的对象也被销毁。不论weak_ptr是否指向了它。
用途
在不影响 智能指针所指向对象的生存周期的同时,判断对象是否存在(使用 lock
方法),从而避免 访问一个不存在的对象 的情况。
例子
void test_shared_ptr() {
std::shared_ptr<int> sp = std::make_shared<int>(3);
cout << *sp << endl;
std::weak_ptr<int> wp(sp);
cout << wp.use_count() << endl;
//expired含义
//判断use_count是否为0? 如果为0,返回true,否则返回false
if (wp.expired()) {
cout << "obj is deleted" << endl;
} else {
cout << "obj exist" << endl;
}
sp.reset();
//lock的含义:
//判断wp指向的对象是否存在?
//如果存在,则返回非空(指向w的对象的智能指针);否则返回空(智能指针)
auto ret = wp.lock();
if (ret) {
cout << "obj exist" << endl;
} else {
cout << "obj not exist" << endl;
}
sp.reset();
if (sp == nullptr) {
cout << "reset will assign nullptr" << endl;
}
sp.reset();
sp.reset();
cout << "it is safe to call reset any times" << endl;
}
输出结果:
3
1
obj exist
obj not exist
reset will assign nullptr
it is safe to call reset any times
unique_ptr
概念
一个unique_ptr 拥有
它所指向的对象。
- 与sharedptr不同,某个时刻只能有一个uniqueptr指向一个给定对象。
- 当uniqueptr被销毁时,它所指向的对象也被销毁。
uniqueptr不支持普通的拷贝和赋值操作,但是可以通过release or reset来转移所有权。
用途
管理一些对象:不需要被共享,但也希望能够在超出作用域时,自动释放资源。
例子
void test_uniq_ptr() {
std::unique_ptr<int> p1(new int(3));
// std::unique_ptr<int> b;
// b = a; //不支持赋值操作
// std::unique_ptr<int> b = a; //不支持拷贝构造
//把p1指向的对象转移给p2
std::unique_ptr<int> p2(p1.release());// release操作会自动给p1赋空
cout << *p2 << endl;
if (p1 == nullptr) {
cout << "p1 is nullptr" << endl;
}
std::unique_ptr<int> p3(new int(4));
//reset 会释放本对象
//release 会转移所有权
p2.reset(p3.release()); //p2释放原来的对象,同时指向p3所指向的对象,p3赋空
if (p3 == nullptr) {
cout << "p3 is nullptr" << endl;
}
//释放对象,同时赋空
p2.reset();
if (p2 == nullptr) {
cout << "p2 is nullptr" << endl;
}
//release操作会放弃当前指针的控制权,但是并不会销毁它。
//因此通常调用release操作
// 是为了 赋值 给另一个智能指针,
// 而不是 为了销毁对象
std::unique_ptr<int> p(new int(3));
auto px = p.release(); //让出指针所有权,但是并没有销毁
delete px; //这里需要手动delete px
}
循环引用计数问题
https://blog.csdn.net/daniel_ustc/article/details/23096229
问题复现
#include <iostream>
using namespace std;
namespace cycle_ref {
class B;
class A {
public:
std::shared_ptr<B> spb;
void dosomething() {
}
~A() {
cout << "A dtor" << endl;
}
};
class B {
public:
std::shared_ptr<A> spa;
~B() {
cout << "B dtor" << endl;
}
};
}
int main(int argc, const char * argv[]) {
{
using namespace cycle_ref;
shared_ptr<cycle_ref::A> sa(new cycle_ref::A());
shared_ptr<cycle_ref::B> sb(new cycle_ref::B());
if (sa && sb) {
sa->spb = sb;
sb->spa = sa;
}
cout << "sa user_count:" << sa.use_count() << endl;
cout << "sb user_count:" << sb.use_count() << endl;
}
}
代码运行结果:
sa user_count:2
sb user_count:2
问题:并没有调用析构函数,说明内存并没有被释放,有内存泄漏。
原因:
当程序退出时,系统会调用sb和sa的析构函数,析构函数会递减引用计数。
如果count为0,则删除该内存;否则不删除内存。
这里count为2,递减了计数器后,count仍然不为0,则不删除内存对象,自然调用对象的析构函数。
用weak_ptr破局---变shared_ptr为weak_ptr
namespace cycle_ref {
class B;
class A {
public:
// std::shared_ptr<B> spb;
std::weak_ptr<B> spb;
void dosomething() {
std::shared_ptr<B> pb = spb.lock();
if (pb) {
cout << "in dosomething: sb use_count:" << pb.use_count() << endl;
}
}
~A() {
cout << "A dtor" << endl;
}
};
class B {
public:
std::shared_ptr<A> spa;
~B() {
cout << "B dtor" << endl;
}
};
}
int main(int argc, const char * argv[]) {
{
using namespace cycle_ref;
shared_ptr<cycle_ref::A> sa(new cycle_ref::A());
shared_ptr<cycle_ref::B> sb(new cycle_ref::B());
if (sa && sb) {
sa->spb = sb; //因为spb是weak_ptr,因此这里不会递增sb的引用计数
sb->spa = sa;
}
sa->dosomething();
cout << "sa use_count:" << sa.use_count() << endl;
cout << "sb use_count:" << sb.use_count() << endl;
}
}
代码运行结果:
in dosomething: sb use_count:2
sa use_count:2
sb use_count:1
B dtor
A dtor
解释:
- 在
dosomething
函数中,spb的引用计数为2。因为其中的lock
函数返回了一个智能指针sb
- 因为使用了weak_ptr,所以sb的引用计数为1。
- 析构流程如下:
- 函数退出时,先析构sb对象,释放B对象,析构spa对象,递减sa的引用计数为1,
- 再析构sa对象,释放A对象。
reference
http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html