智能指针
解决常规指针在销毁时为释放指向的变量而导致内存泄露的问题, 因此对常规指针进行了封装, 利用析构函数来对指针指向的内存进行释放.
智能指针分类
在stl里面有四种智能指针, auto_ptr、unique_ptr、shared_ptr和weak_ptr. 需要使用智能指针需要先包含头文件(需注意, 这些智能指针的构造函数都用explicit声明, 所以不能用隐式转换的方式进行初始化):
#include <memory>
auto_ptr
auto_ptr在C++ 98开始被使用, C++11被抛弃.
内部实现
auto_ptr对其封装的指针是独占的, 在其内部通过重载了拷贝构造函数和赋值运算符, 在一个拥有指针p的auto_ptr aP1通过拷贝构造函数赋给了另一个aP2之后, aP1对于p的所有权就交给了aP2
代码实现
auto_ptr是C++98开始使用的, 如果有以下代码:
class Test
{
public:
Test(int a = 0 ) : m_a(a) { }
~Test( )
{
cout << "Calling destructor" << endl;
}
public: int m_a;
};
void main( )
{
std::auto_ptr<Test> p( new Test(5) );
cout << p->m_a << endl;
}
上述代码在堆区申请申请了一个Test大小的内存并让智能指针指向这块内存, 而智能指针在析构的时候会自动释放掉这段内存.
不足
- 当一个auto_ptr aP1指向了资源A, 在用aP1给aP2赋值, 这样aP2就拥有了资源A且aP1就不会再拥有, 这样使用aP1就是有风险的, 且其作为函数参数传递也会转移其所有权.
- 不能指向对象数组, 不能指向一组对象
- 不能同标准容器一块儿使用, C++的STL容器对于容器元素类型的要求是有值语义,即可以赋值和复制。auto_ptr在赋值和复制时都进行了特殊操作,所以auto_ptr对象不能作为STL容器元素。
现在基本已经不使用auto_ptr.
unique_ptr
构造
由于auto_ptr的一些不足, 所以有unique_ptr, unique_ptr构造函数不支持复制构造, 也不支持隐式构造. 同样, 该指针也是对其拥有资源的独占.
所有权转移操作
std::unique_ptr<int> uP1(new int(5)) //构造一个指向资源5的unique_ptr
unique_ptr<int> uP2 = std::move(uP1) //转移资源5的所有权至uP2, 转移后uP1
//就不能继续使用
操作汇总
unique_ptr<ele_type, del_type> uP(del) //del为del_type类型的删除器
//这样可以指定删除器
uP.release() //接触对于当前资源的控制权并返
//回其所有的指针, 记得接收返回
//否则会造成内存泄露
uP.reset() //解除对于当前资源的拥有权并指向
//指向参数, 同时原有资源释放掉
作为函数参数传递
虽然unique_ptr不能进行拷贝和赋值, 但是其作为函数内部变量快被销毁时可以作为返回值(编译器会自动调用std::move).
shared_ptr
C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。
构造
可以使用:
std::shared_ptr<int> p3 = std::make_shared<int>(10);
的方式构造一个智能指针.
举例说明:
int main()
{
string *s1 = new string("s1");
shared_ptr<string> ps1(s1);
shared_ptr<string> ps2;
ps2 = ps1;
cout << ps1.use_count()<<endl; //2
cout<<ps2.use_count()<<endl; //2
cout << ps1.unique()<<endl; //0
string *s3 = new string("s3");
shared_ptr<string> ps3(s3);
cout << (ps1.get()) << endl; //033AEB48
cout << ps3.get() << endl; //033B2C50
swap(ps1, ps3); //交换所拥有的对象
cout << (ps1.get())<<endl; //033B2C50
cout << ps3.get() << endl; //033AEB48
cout << ps1.use_count()<<endl; //1
cout << ps2.use_count() << endl; //2
ps2 = ps1;
cout << ps1.use_count()<<endl; //2
cout << ps2.use_count() << endl; //2
ps1.reset(); //放弃ps1的拥有权,引用计数的减少
cout << ps1.use_count()<<endl; //0
cout << ps2.use_count()<<endl; //1
}
2.4 weak_ptr
share_ptr还是有一种情况会发生内存泄露,也就是两个share_ptr相互引用的时候,如以下代码:
class B;
class A {
public:
share_ptr<B> _pb;
~A(){
printf("A delete\n");
}
};
class B {
public:
share_ptr<A> _pa;
~B(){
printf("B delete\n");
}
};
int main() {
share_ptr<A> spA(new A());
share_ptr<B> spB(new B());
cout << spA.use_count() << endl; // 1
cout << spB.use_count() << endl; // 1
spA._pb = spB;
cout << spB.use_count() << endl; // 2
spB._pa = spA;
cout << spA.use_count() << endl; // 2
return 0;
}
函数是这样的,share_ptr spA指向一个新的A对象,假设为objA,那么此时objA的引用计数为1次。share_ptr spB指向一个新的B对象,假设为objB,那么此时objB的引用计数为1。让spA._pb = spB,也就是让spA._pb也指向objB,那么此时objB的引用计数为2。让spB._pa = spA,也就是让spB._pa也指向objA,那么此时objA的引用计数也为2。接下来进行析构,发现此时栈上从底至定分别为spA,spB,那么先进行spB的析构,spB指向objB的引用计数为2,减1为1(此时objA的_pb还引用着),不释放。再进行spA的析构,spA指向objA的引用计数也为2,减1为1,也不释放,这样退出函数后,堆上面的objA和objB都没有释放,违背了share_ptr设计的初衷。
可以通过把其中一个类中的share_ptr改为weak_ptr解决以上问题。
weak_ptr是用来配合share_ptr工作的,可以用share_ptr对其赋值,赋值后weak_ptr同样指向share_ptr指向的对象,但是不能像share_ptr一样直接对对象进行操作,必须通过weak_ptr成员函数,返回一个share_ptr,才能进行操作。也可以用另一个weak_ptr对其进行赋值。