内存管理需要注意的点
- 内存泄漏
- 野指针
- 访问越界
为了避免这些问题,智能指针采用RAII机制 —资源获取既初始化
- 所有初始化操作移到对象的构造函数中
- 所有的释放操作都放在对象的析构函数里
- 适当的异常处理代码来应付对象构造期间丢出的异常(分配内存的时候)
好处:对象创建后,用户能开始正确使用对象,不用担心对象的有效性或者是否还要作进一步的初始化操作。
scoped_ptr 不能拷贝,赋值。
侯捷大师说过,源码面前,了无秘密
于是就去找一下源码,源码中将 拷贝赋值函数设置为 private,并且函数里面为空语句。
不适用于stl 类似的容器中
weak_ptr weak_ptr 可以说具有观察权。
准确的说不能算作一种智能指针,它的出现是用来弥补 share_ptr 的缺点,例如循环引用。
它有一个lock成员函数,可以将弱引用转化为 share_ptr。
其中最常用的就是share_ptr了,它也被收入到了c++ 11 标准中
- share_ptr 用了设计模式中的 代理模式,用share_ptr 代理指针,所以它获得了指针相关的操作,比如解引用等等
- 不可以使用变址操作符(因为指针越界大部分是因为变址操作符导致的)。
- share_ptr 不需要手动的调用类似release 方法。
- share_ptr 只会所有权转移 --rest()
share_ptr 源码摘要
template<class T>
class shared_ptr
{
//创建一个持有空指针的share_ptr,use_count = 0 && get() == NULL
shared_ptr() BOOST_NOEXCEPT : px( 0 ), pn() // never throws in 1.30+{}
//获得一个指向类型T的指针p的管理权,Y 必须能够转换为T类型
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
//作用同上,增加了一个构造函数D,是一个仿函数对象,代表删除器
template<class Y, class D>
shared_ptr( Y * p, D d ): px( p ), pn( p, d )
//析构函数:资源引用计数减1 同时判断引用计数为0,并且 管理的资源不为NULL,并且判断是否有删除器,如果有删除器,调用删除器,如果没有删除器,调用delete
~shared_ptr();
};
这里为什么要有删除器呢?
例如:fopen(),fclose() 操作打开的资源。这是delete 没用的。
构造函数获得管理权,
拷贝赋值操作 共享管理权
至于shared_ptr 的成员函数,这里就只说一下reset();
void reset();
template<class Y> void reset(Y* p);
template<class Y,class D> void reset(Y* p);
reset(): 如果该share_ptr 拥有某个管理权的话,它会将引用计数减1,如果资源计数器为0,并且指针不为NULL,删除原来共享的资源。将指针赋为 NULL
template void reset(Y* p); --转移管理权
首先判断该share_ptr 是否拥有管理权,如果有的话,,它会将引用计数减1,如果资源计数器为0,它会释放掉原来所指向的资源或指针,让他delete,然后它会将它内部的T 类型的指针,设置为参数 Y 的指针,这样就获得了 Y 类型 的 p 指针的管理权,然后将引用计数设为1。
shared_ptr 使用时出现的常见问题
1.shared_ptr 多次引用同一内存数据导致程序崩溃
No code , No BB
class Foo
{
public:
Foo(string s) : m_s(s) {
}
~Foo()
{
cout << "Foo 开始析构" << endl;
}
private:
string m_s;
};
int main()
{
Foo* pFoo = new Foo("test");
shared_ptr<Foo> p1(pFoo);
shared_ptr<Foo> p2(pFoo);
return 0;
}
运行结果:
可以看到,将Foo() 析构了两次。
解决方案:
1.使用匿名 new操作,资源获取既初始化
shared_ptr<Foo> p1(new Foo("test"));
2.使用boost 工厂函数
2.shared_ptr 循环引用导致内存泄露
class B; // 前置声明
class A {
public:
A()
{
cout << "A 构造" << endl;
}
~A()
{
cout << "A 析构" << endl;
}
shared_ptr<B> ptr;
};
class B {
public:
B()
{
cout << "B 构造" << endl;
}
~B()
{
cout << "B 析构 " << endl;
}
shared_ptr<A> ptr;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa -> ptr = pb; // 1
pb -> ptr = pa; //2
return 0;
}
运行结果:
结果没有调用析构函数。
什么原因呢?
当执行 1 时,pb 指针的引用计数加 1
当执行 2 时,pa 指针的引用计数加 1
离开主函数时,两个指针的引用计数减一,不为 0 ,所以不调用析构函数。
解决方法:
weak_ptr
1.从上面的例子可以看出,引用计数是一种很便利的内存管理,但是有一个缺点,那就是不能管理循环引用或自引用对象,然后就引入了 weak_ptr 。
2.它是与shared_ptr 同时使用的,它更像是shared_ptr 的助手而不是智能指针,因为它不具备普通指针的行为,没有重载operaotr * 和 -> 操作符。这是特意的。这样他就不能共享指针,不能操作资源,这正是它"弱"的原因,它最大的作用是协助shared_ptr 工作,像旁观者那样观察资源的使用情况
3.weak_ptr 可以从一个shared_ptr 或另外一个weal_ptr 构造,从而获得资源的观察权,但weak_ptr 并没有共享资源,它的构造并不会引起引用计数的增加,同事它的析构也不会引起引用计数的减少,它仅仅是观察者。
4.weak_ptr 的lock 成员函数 的返回值是一个shared_ptr 类型的指针。
make_shared< T >模板工厂函数
1.用于不需要使用删除器的情况下(仅仅使用 new /delete 进行内存分配和析构的类上面)
有数shared_ptr 显式的消除了delete 操作符的调用,因此使用该函数也可以显式的消除了 new 操作符的调用
2.调用该函数比直接创建shared_ptr 对象的方式快且高效,因为它内部仅分配一次内存,消除了shared_ptr 构造时的开销,建议在满足情况的基础上尽量使用该函数
3.它具有可变模板参数特性,如果c++ 编译器支持c++ 11 的可变参数模板特性,那么该工厂函数的参数数量没有限制,否则它只能接受最多 10 个参数被传递到 T 的构造函数参数中去。