前言
动态内存管理引起的所谓内存泄漏的问题是编程领域的一大顽疾,它的成因非常直截了当---“只拿不还”,但它产生的缘由有时却非常隐晦需要非常仔细布局你的每一行代码才能抵抗它们对你的系统的侵蚀。而智能指针就能解决上面的问题,它的主要的功能就是自动帮我们管理内存。
一、智能指针
1、智能指针主要解决以下问题
- 内存泄漏:内存手动释放,使用智能指针可以自动释放
- 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题
auto_ptr- shared_ptr 共享对象的所有权,但性能略差。
- unique_ptr 独占对象的所有权,由于没有引用计数,因此性能较好。
- weak_ptr 配合shared_ptr,解决循环引用的问题
3、智能指针的使用
3.1 shared_ptr
3.1.1 内存模型:
- std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存,每拷贝一次引用计数reference count就加1,每一次析构就减1。再最后一个shared_ptr析构的时候,内存才会被释放。
- shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个 shared_ptr对象销毁时,被管理对象自动销毁。

3.1.1 基本用法和函数
shared_ptr的初始化
// 初始化的三种方式,优选第1 和第 2种,因为make_shared是构造函数,更高效auto s = make_shared<int>(100);shared_ptr<int> s= make_shared<int>(100);shared_ptr<int> sp1(new int(100));//不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:std::shared_ptr<int> p = new int(1);
shared_ptr的常用函数
- s.get(): 功能:返回shared_ptr中保存的裸指针;
-
s.use_count() :功能:返回shared_ptr的强引用计数;
-
s.unique() : 功能:若use_count()为1,返回true,否则返回false。
- s.reset(…): 功能:重置shared_ptr;
- reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯一指向该对象的指针,则引用计数减少1,同时将P置空。
- reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指针,则只减少引用计数,并指向新的对象。如:
auto s = make_shared<int>(100);s.reset(new int(200));
std::shared_ptr<int> ptr(new int(1));int *p = ptr.get(); //delete p; //不小心释放 就会造成double free错误p.get()的返回值就相当于一个裸指针的值,谨慎使用p.get()的返回值
- 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
- 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
- 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误
指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
#include <iostream>
#include <memory>
using namespace std;
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
int main()
{
std::shared_ptr<int> p(new int(1), DeleteIntPtr);
return 0;
}
std::shared_ptr<int> p(new int(1), [](int *p) {cout << "call lambda delete p" << endl;delete p;});
std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});
3.1.2 使用shared_ptr要注意的问题
不要用一个原始指针初始化多个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指针
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。
/*
错误范例:
由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系
的,在离开作用域之后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的shared_ptr的做法是:
让目标类通过std::enable_shared_from_this基类的方法shared_from_this()来返回一个新的
std::shared_ptr 对象来作为this的shared_ptr,如下所示。
*/
#include <iostream>
#include <memory>
using namespace std;
class A: public std::enable_shared_from_this<A>
{
public:
shared_ptr<A>GetSelf()
{
return shared_from_this(); //正确的做法
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf(); // ok
return 0;
}
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
3.2 unique_ptr独占的智能指针
- unique_ptr是一个独占型的智能指针,不能将其赋值给另一个unique_ptr
- unique_ptr可以指向一个数组
- unique_ptr需要确定删除器的类型
3.2.1 unique_ptr使用特点
1、unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再拥有原来指针的所有权了。例如unique_ptr<T> my_ptr(new T); // 正确unique_ptr<T> my_other_ptr = std::move(my_ptr); // 正确unique_ptr<T> ptr = my_ptr; // 报错,不能复制 2、unique_ptr可以指向一个数组,而shared_ptr不可以,代码如下所示std::unique_ptr<int []> ptr(new int[10]);ptr[9] = 9;std::shared_ptr<int []> ptr2(new int[10]); // 这个是不合法的3、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;}); // 错误unique_ptr需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,可以这样写:std::unique_ptr<int, void(*)(int*)> ptr5(new int(1), [](int *p){delete p;}); //正确
3.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 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
3.3.1 weak_ptr的基本用法
1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:
shared_ptr<int> sp(new int(10));weak_ptr<int> wp(sp);cout << wp.use_count() << endl; //结果讲输出12. 通过expired()方法判断所观察资源是否已经释放,如下所示:shared_ptr<int> sp(new int(10));weak_ptr<int> wp(sp);if(wp.expired())cout << "weak_ptr无效,资源已释放";elsecout << "weak_ptr有效";3. 通过lock方法获取监视的shared_ptr,如下所示: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;}
#include <iostream>
#include <memory>
using namespace std;
class A: public std::enable_shared_from_this<A>
{
public:
shared_ptr<A>GetSelf()
{
return shared_from_this(); //
}
~A()
{
cout << "Destructor A" << endl;
}
};
int main()
{
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf(); // ok
return 0;
}
在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的,因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,spy的引用计数减为0,A对象会被析构,不会出现A对象被析构两次的问题。
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}
1. weak_ptr在使用前需要检查合法性。weak_ptr<int> wp;{shared_ptr<int> sp(new int(1)); //sp.use_count()==1wp = sp; //wp不会改变引用计数,所以sp.use_count()==1shared_ptr<int> sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象}shared_ptr<int> sp_null = wp.lock(); //sp_null .use_count()==0;因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法直到自己所容纳的那个指针资源的当前状态。
3.4 智能指针安全性问题
情况1:多线程代码操作的是同一个shared_ptr的对象,此时是不安全的。比如std::thread的回调函数,是一个lambda表达式,其中引用捕获了一个shared_ptrstd::thread td([&sp1]()){....});又或者通过回调函数的参数传入的shared_ptr对象,参数类型引用void fn(shared_ptr<A>&sp) {...}..std::thread td(fn, sp1);这时候必然不是线程安全的。情况2:多线程代码操作的不是同一个shared_ptr的对象这里指的是管理的数据是同一份,而shared_ptr不是同一个对象。比如多线程回调的lambda的是按值捕获的对象。std::thread td([sp1]()){....});另一个线程传递的shared_ptr是值传递,而非引用:void fn(shared_ptr<A>sp) {...}..std::thread td(fn, sp1);这时候每个线程内看到的sp,他们所管理的是同一份数据,用的是同一个引用计数。但是各自是不同的对象,当发生多线程中修改sp指向的操作的时候,是不会出现非预期的异常行为的。也就是说,如下操作是安全的。void fn(shared_ptr<A>sp) {...if(..){sp = other_sp;} else {sp = other_sp2;}}需要注意:所管理数据的线程安全性问题。显而易见,所管理的对象必然不是线程安全的,必然 sp1、sp2、sp3智能指针实际都是指向对象A, 三个线程同时操作对象A,那对象的数据安全必然是需要对象A自己去保证。

182

被折叠的 条评论
为什么被折叠?



