什么是智能指针
智能指针的本质是将指针封装为类成员对象,并在析构函数中删除指针指向的内存,主要的作用是对作用域内的动态分配内存对象进行自动施放,解决使用指针带来的内存泄漏问题。
1. auto_ptr
特点:这种类型的指针在进行拷贝构造的时候,会将自己的值赋值給另外一个指针,然后将自己指针的内容进行清空。
auto_ptr的使用
- 基本类型
auto_ptr<int> ptr(new int(100));
cout << *ptr << endl;
*ptr = 20;
cout << *ptr << endl;
auto_ptr<int> ptr3(ptr);
cout << *ptr3 << endl;
// cout << *ptr << endl; // 此时ptr已经被清空
运行结果为:
- 类类型
auto_ptr<Simple>ptr2(new Simple); // Simple是一个类
ptr2->Func();
类的定义如下:
class Simple{
public:
void Func(){
cout << "Simple::Func" << endl;
}
};
运行结果为:
缺陷:
- 两个auto_ptr不能同时拥有同一个对象
- auto_ptr不能管理数组指针
- auto_ptr被拷贝或被赋值后,失去对原指针的管理.这种情况被称为指针所有权传递。
- auto_ptr不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作。
注意:不建议使用auto_ptr,目前使用较多的是unique_ptr和shared_ptr
auto_ptr的实现代码
class auto_ptr{
T* ptr;
public:
auto_ptr(T* ptr):ptr(ptr){}
auto_ptr(const auto_ptr& p):ptr(p.ptr){
p.ptr = nullptr;
};
auto_ptr& operator=(const auto_ptr& p){
if(this == &p) return *this;
ptr = p.ptr;
p.ptr = nullptr;
return *this;
}
~auto_ptr(){
delete ptr;
}
T& operator*(){ return *ptr; }
T* operator->(){ return &ptr; }
bool operator==(const auto_ptr& p)const{ return ptr == p.ptr;}
bool operator!=(const auto_ptr& p)const{ return ptr != p.ptr;}
};
2. unique_ptr
特点:无法进行复制和赋值
注:复制:
unique_ptr ptr1(ptr2)
赋值:
ptr1 = ptr2
unique_ptr
的用法
unique_ptr<int>ptr4(new int(123));
cout << *ptr4 << endl;
*ptr4 = 200;
cout << *ptr4 << endl;
unique_ptr<Simple>ptr5(new Simple);
ptr5->Func();
代码运行结果为:
unique_ptr的实现代码
template<class T>
class unique_ptr{
T* ptr;
public:
unique_ptr(T* ptr):ptr(ptr){}
unique_ptr(const unique_ptr& p) = delete;
unique_ptr& operator=(const unique_ptr& p) = delete;
~unique_ptr(){
delete ptr;
}
T& operator*(){ return *ptr; }
T* operator->(){ return &ptr; }
bool operator==(const unique_ptr& p)const{ return ptr == p.ptr;}
bool operator!=(const unique_ptr& p)const{ return ptr != p.ptr;}
};
3. shared_ptr
shared_ptr相比较unique_ptr可以进行赋值和复制
shared_ptr
可以进行复制和赋值而不需要担心二次释放的问题的原因是,在该类型的指针中由一个计数器,每次赋值和复制的时候都会进行计数,只有当该计数器值为 0 的时候才会释放对应指向的空间。工作过程如图所示:
share_ptr的使用如下:
shared_ptr<int> ptr7(new int(1000));
cout << *ptr7 << endl;
shared_ptr<int> ptr8(ptr7);
cout << *ptr7 << endl;
cout << *ptr8 << endl;
运行结果如下:
shared_ptr缺陷:
- 当出现循环引用的时候,由于类对象的析构必须从类的成员开始,就会出现循环的情况,具体如下。
示例如下:
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
~A() { cout <<"destroying A\n"; }
public:
shared_ptr<B> m_b;
};
class B {
public:
~B() { cout <<"destroying B\n"; }
public:
shared_ptr<A> m_a;
};
int main(){
shared_ptr<A> a(new A());
shared_ptr<B> b(new B);
a->m_b = b;
b->m_a = a;
}
运行这个程序,会发现该程序不会调用析构函数,这是因为对象
a
析构的时候,需要析构它的成员变量
m_b
,但是
m_b
进行析构的又需要调用类
B
的析构函数,类B进行析构的时候需要对它的成员变量进行析构,又需要调用类
A
的析构,这样就会形成循环,从而无法进行析构。
4. weak_ptr
weak_ptr
就可以解决
shared_ptr
的循环引用的问题。
我们在shared_ptr遇到的循环引用问题就我们就可以使用wesk_ptr来是协助,我们将上述类
B
中的智能指针更换为
weak_ptr
类指针,具体代码如下:
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
~A() { cout <<"destroying A\n"; }
public:
shared_ptr<B> m_b;
};
class B {
public:
~B() { cout <<"destroying B\n"; }
public:
weak_ptr<A> m_a;
};
int main(){
shared_ptr<A> a(new A());
shared_ptr<B> b(new B);
a->m_b = b;
b->m_a = a;
}
运行结果如下:
这时程序运行结束后可以成功进行析构。
weak_ptr
的好处
- 打破递归的依赖关系
- 使用一个共享的资源但是不要所有权,不添加引用计数
- 避免悬空指针。
总结
注意:
- 虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案。
- 如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。
因此,不要认为只要使用了智能指针便能杜绝内存泄漏。