智能指针
std::auto_ptr
#include <iostream>
#include <memory>
int main()
{
std::auto_ptr<int> ptr(new int);
std::auto_ptr<int> ptr1 = ptr;
std::cout << ptr.get() << std::endl;
std::cout << ptr1.get() << std::endl;
return 0;
}
std::auto_ptr
的主要功能是实现自动的资源管理。
在上述操作后,ptr
的值为空,也即指针的拷贝将指针的值改变了,这和普通指针的拷贝语义是不一致的。C++11标准中已将std::auto_ptr
标记为弃用。
std::shared_ptr
即共享指针,其支持自动资源管理并实现了拷贝的语义。
使用共享指针管理对象时,需要注意同一个对象只能由一个共享指针及其拷贝来管理,否则就会出现多次释放。
共享指针引起泄漏的情况
使用共享指针管理对象时,需要注意避免循环引用。若两个对象均通过共享指针持有另一个对象,则由于循环引用在离开作用域时无法自动释放造成资源泄漏。
循环引用的一种特殊形式是自引用,也即一个对象通过共享指针持有自己的指针。
循环引用的例子:
#include <iostream>
#include <memory>
class BB;
class AA
{
public:
std::shared_ptr<BB> ptr_bb;
AA() {std::cout << "AA +" << std::endl;}
~AA() {std::cout << "AA -" << std::endl;}
};
class BB
{
public:
std::shared_ptr<AA> ptr_aa;
BB() {std::cout << "BB +" << std::endl;}
~BB() {std::cout << "BB -" << std::endl;}
};
int main()
{
std::shared_ptr<AA> ptr_aa(new AA);
std::shared_ptr<BB> ptr_bb(new BB);
ptr_aa->ptr_bb = ptr_bb;
ptr_bb->ptr_aa = ptr_aa;
return 0;
}
执行结果如下:
./a.out
AA +
BB +
自引用的例子如下:
#include <iostream>
#include <memory>
class AA
{
public:
std::shared_ptr<AA> ptr_bb;
AA() {std::cout << "AA +" << std::endl;}
~AA() {std::cout << "AA -" << std::endl;}
};
int main()
{
std::shared_ptr<AA> ptr_aa(new AA);
ptr_aa->ptr_bb = ptr_aa;
return 0;
}
执行结果如下:
./a.out
AA +
循环引用引起泄漏的原因(技术角度)
首先从技术角度来分析引起泄漏的原因。
共享指针内部是通过引用计数来实现对资源的管理的。每当共享指针被复制一次,计数加1,每当共享指针被析构,计数减1,当计数被减至0时,才真正执行被管理对象的销毁。值得注意的是,共享指针的计数对于拷贝得到的共享指针是相同的(内部实现上,通过拷贝得到的共享指针内部均有一个指向同一个计数值的指针)。
先看一个正常的示例:
#include <memory>
#include <iostream>
class AA
{
public:
AA() {std::cout << "AA +" << std::endl;}
~AA() {std::cout << "AA -" << std::endl;}
};
int main()
{
std::shared_ptr<AA> ptr_aa(new AA());
std::shared_ptr<AA> ptr_aa_copy;
ptr_aa_copy = ptr_aa;
return 0;
}
主函数中先创建了一个共享指针ptr_aa
,管理一个通过new
创建的对象AA
。然后创建了一个共享指针ptr_aa_copy
,并通过拷贝赋值的方式成为了ptr_aa
的拷贝,因此这两个共享指针的引用计数均为2。在离开main
函数时,一共有两个对象需要被销毁,因此引用计数最终变为0,即最终会完成资源的销毁。
以下为出现泄漏的循环引用的main
函数部分:
int main()
{
std::shared_ptr<AA> ptr_aa(new AA);
std::shared_ptr<BB> ptr_bb(new BB);
ptr_aa->ptr_bb = ptr_bb;
ptr_bb->ptr_aa = ptr_aa;
return 0;
}
在这段代码中,首先创建了两个共享指针,然后对这两个共享指针分别执行了一次拷贝,即这两个共享指针的引用计数均为2。在主函数结束时,这两个共享指针需要销毁,因此这两个共享指针对象的析构函数将被执行(注意,不是AA对象和BB对象的析构函数)。在执行ptr_aa
共享指针对象的析构函数时,引用计数将由2减为1,由于引用计数不为0,其持有的AA
对象不会被销毁,同样在ptr_bb
共享指针对象销毁的时候,其持有的BB
对象也不会被销毁。因此造成了内存的泄漏。
循环引用使得在离开作用域时,所有的共享指针的引用计数均没有降到0,从而所有被持有的对象均没有被析构。
循环引用引起泄漏的原因(语义角度)
共享指针std::shared_ptr
会持有一个对象,这意味着多个共享指针持有同一个对象时,只要有一个共享指针没有被销毁时,这个被持有的对象就不会被销毁。这种持有的方式称为强引用,与其对应的是弱引用,普通的指针是一种弱引用,C++也提供了std::weak_ptr
用于弱引用。
如果AA
对象持有一个BB
对象的共享指针,BB
对象持有一个CC
对象的共享指针,依次类推,则产生了CC
对象的释放依赖BB
对象的释放,BB
对象的释放依赖AA
对象的释放的这种依赖链。很容易想到,如果在程序中产生了这种依赖链,则必须将整个依赖关系设计为一个有向无环图(DAG),这样才能确保正确的释放。
循环引用的破解之法
厘清持有关系和引用关系
持有关系使用强引用,引用关系使用弱引用。
一个例子是二叉树的节点,其中三个指针left
,right
,parent
。由于我们需要通过上层节点去管理下层节点,因此left
、right
引用为强引用,parent
引用为弱引用。
避免依赖成环
如果一个对象持有另一个对象的共享指针,就需要注意形成的依赖关系是否成环。
下面分析一个正常的依赖的释放流程:
#include <memory>
#include <iostream>
class AA;
class BB;
class CC;
class AA
{
public:
AA() {std::cout << "AA +" << std::endl;}
~AA() {std::cout << "AA -" << std::endl;}
std::shared_ptr<BB> bb;
};
class BB
{
public:
BB() {std::cout << "BB +" << std::endl;}
~BB() {std::cout << "BB -" << std::endl;}
std::shared_ptr<CC> cc;
};
class CC
{
public:
CC() {std::cout << "CC +" << std::endl;}
~CC() {std::cout << "CC -" << std::endl;}
};
int main()
{
std::shared_ptr<AA> a(new AA());
std::shared_ptr<BB> b(new BB());
std::shared_ptr<CC> c(new CC());
a->bb = b;
b->cc = c;
return 0;
}
这里的依赖关系为AA->BB->CC
,没有形成环状依赖。main
函数中一共有三个共享指针,其中aa
的引用计数为1,bb
和cc
的引用计数为2。当离开main
函数时,aa
、bb
、cc
的析构函数均被执行。当aa
的析构函数执行时,引用计数减为0,其持有的AA
对象被析构,从而引起其成员bb
被析构,从而b
的引用计数降为1(注意,持有同一个对象的共享指针共享同一个引用计数)。当bb
的析构函数执行时,引用计数减为0,其持有的BB
对象被析构,从而引起其成员cc
被析构,c
的引用计数降为1。最终cc
的析构函数执行时,引用计数减为0,其持有的CC
对象被析构。全部对象被析构完毕。
因此最终执行结果为:
./a.out
AA +
BB +
CC +
AA -
BB -
CC -
标签:AA,std,aa,智能,共享,ptr,指针
From: https://www.cnblogs.com/amazzzzzing/p/17031799.html