目录引导:
智能指针是什么
智能指针的使用场景
智能指针的使用方法
一. 智能指针是什么 ‘
智能指针是C++11提供的一种类,采用RAII的高级编程设计思想,可以自动管理类的对象的生命周期;感兴趣的朋友可以阅读《Modern C++》这本书,该书作者在书中有具体的讲解。在C++11中有三个智能指针:shared_ptr,unique_ptr, weak_ptr。unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。 shared_ptr共享对象的所有权,但性能略差。 weak_ptr配合shared_ptr,解决循环引用的问题。
二. 智能指针的使用场景
智能指针主要解决以下问题: 1. 内存泄漏:内存手动释放,使用智能指针可以自动释放 malloc free; new delete 2. 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题。
三. 智能指针的使用方法
1,shared_ptr的使用方法
(1)shared_ptr的内存模型:
std::shared_ptr<T>在栈上创建,它的内存模型有两个部分:1.Ptr to T; 2.Ptr to Control Block。一个智能指向指针对象的指针,该对象在堆区;一个指向控制块的指针。控制块中包含一个 引用计数(reference count), 一个弱计数(weak count)和其它一些数据。示意图如下:
当一个share_ptr<T>对象离开作用域,或者调用reset()函数,则相应的Reference Count减1,当Refernce Count减到0的时候,就会去释放堆上的指针,该share_ptr<T>指针被析构。
(2) shared_ptr的基本用法和常用函数:
s.get():返回shared_ptr中保存的裸指针;
s.reset(…):重置shared_ptr;
reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯 一指向该对象的指针,则引用计数减少1,同时将P置空。 reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指 针,则只减少引用计数,并指向新的对象。如:
auto s = make_shared(100);
s.reset(new int(200));
s.use_count() :返回shared_ptr的强引用计数;
s.unique() :若use_count()为1,返回true,否则返回false。
初始化shared_ptr<T>p的方法有下面几种
shared_ptr<int>sp1(new int(10));
shared_ptr<int>sp2=sp1;
shared_ptr<int>sp3;
sp3.reset(new int(12);
shared_ptr<int>sp4=make_shared<int>(10);//这种方法更高效,我们应该采用这种方法
错误的初始化方式:
不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:
std::shared_ptr<int> p = new int(1);
不要用一个原始指针初始化多个shared_ptr
int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误
不要在函数实参中创建shared_ptr
function(shared_ptr<int>(new int), g()); //有缺陷
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:
shared_ptr<int> p(new int);
function(p, g());
通过shared_from_this()返回this指针
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
shared_ptr<A> GetSelf()
{
return shared_ptr<A>(this); // 不要这么做
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf();
return 0;
}
运行后调用了两次析构函数。 在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系 的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
参考一下的代码,你会更加体会到shared_ptr<T>这个智能指针:
#include<iostream>
#include<memory>
using namespace std;
int main(void) {
shared_ptr<int>sp1(new int(100));
cout << sp1.use_count() << endl;//1;
shared_ptr<int>sp2 = sp1;
cout << sp1.use_count() << endl;//2;
sp2.reset(new int(10));
cout << sp2.use_count() << endl;//1;
cout << sp1.use_count() << endl;//1
sp1.reset();
cout << sp2.use_count() << endl;//1;
cout << sp1.use_count() << endl;//0
}
避免循环引用, 循环引用会导致内存泄漏,比如:
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr<A> aptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<B> bptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->aptr = ap;
bp->bptr = bp;
cout << ap.use_count() << endl;//2
}
cout << "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
无法析构掉A,B这两个堆上的类的的对象。循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导 致两个指针都不会被析构,产生内存泄漏。 解决的办法是把A和B成员变量都改为weak_ptr。
(3)unique_ptr独占的智能指针
1. unique_ptr是一个独占型的智能指针,不能将其赋值给另一个unique_ptr ; unique_ptr可以指向一个数组 ; unique_ptr需要确定删除器的类型
除了unique_ptr的独占性, unique_ptr和shared_ptr还有一些区别,比如: unique_ptr可以指向一个数组,代码如下所示
std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;
std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的
unique_ptr指定删除器和shared_ptr有区别
std::shared_ptr<int> ptr3(new int(1), [](int *p){delete p;}); // 正确
std::unique_ptr<int> ptr4(new int(1), [](int *p){delete p;}); // 错误
而要这样写:
std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); // 正确
关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。 如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个 资源就用shared_ptr。
(3)weak_ptr弱引用的智能指针
概念:
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互 使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。 weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。 weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从 一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
基本用法
1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果输出1
2. 通过expired()方法判断所观察资源是否已经释放,如下所示:
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
3. 通过lock方法获取监视的shared_ptr,如下所示:
#include<iostream>
#include<memory>
using namespace std;
weak_ptr<int> gw;
void f()
{
auto spt = gw.lock();
if (gw.expired()) {
cout << "gw无效,资源已释放";
}
else {
cout << "gw有效, *spt = " << *spt << endl;
}
}
int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp;
f();
}
f();
return 0;
}
输出结果: