文章目录
智能指针的原理
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
常用的智能指针
shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
- 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针;
- 每次创建类的新对象时,初始化指针并将引用计数置为1;
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
share_ptr使用实例
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//构建 2 个智能指针
std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2(p1);
//输出 p2 指向的数据
cout << *p2 << endl; // 10
p1.reset();//引用计数减 1,p1为空指针
if (p1) { // 输出
cout << "p1 不为空" << endl;
}
else {
cout << "p1 为空" << endl;
}
//以上操作,并不会影响 p2
cout << *p2 << endl; // 10
//判断当前和 p2 同指向的智能指针有多少个
cout << p2.use_count() << endl; // 1
system("pause");
return 0;
}
unique_ptr
unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃
std::unique_ptr<int> p4(new int);
std::unique_ptr<int> p5(p4);//错误,堆内存不共享
std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数
值得一提的是,对于调用移动构造函数的 p4 和 p5 来说,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)。
unique_ptr使用实例
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::unique_ptr<int> p5(new int);
*p5 = 10;
std::unique_ptr<int> p10(std::move(p5)); // 此时p5已经被置空
cout << *p10 << endl;
// p 接收 p5 释放的堆内存
//int* p = p5.release();
//cout << *p << endl;
判断 p5 是否为空指针
if (p5) {
cout << "p5 is not nullptr" << endl;
}
else {
cout << "p5 is nullptr" << endl;
}
//std::unique_ptr<int> p6;
p6 获取 p 的所有权
//p6.reset(p);
//cout << *p6 << endl;;
system("pause");
return 0;
}
weak_ptr
weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
weak_ptr使用实例
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::shared_ptr<int> sp1(new int(10));
std::shared_ptr<int> sp2(sp1);
std::weak_ptr<int> wp(sp2);
//输出和 wp 同指向的 shared_ptr 类型指针的数量
cout << wp.use_count() << endl;
//释放 sp2
sp2.reset();
cout << wp.use_count() << endl;
sp1.reset();
cout << wp.use_count() << endl;
//借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
if (wp.lock())
cout << *(wp.lock()) << endl;
else
cout << "nullptr" << endl;
system("pause");
return 0;
}
auto_ptr
主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。
share_ptr的实现
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(new int(1))
{}
SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){
(*_pcount)++;
}
SharedPtr<T>& operator=(const SharedPtr& s){
if (this != &s)
{
if (--(*(this->_pcount)) == 0)
{
delete this->_ptr;
delete this->_pcount;
}
_ptr = s._ptr;
_pcount = s._pcount;
*(_pcount)++;
}
return *this;
}
T& operator*()
{
return *(this->_ptr);
}
T* operator->()
{
return this->_ptr;
}
~SharedPtr()
{
--(*(this->_pcount));
if (*(this->_pcount) == 0)
{
delete _ptr;
_ptr = NULL;
delete _pcount;
_pcount = NULL;
}
}
private:
T* _ptr;
int* _pcount;//指向引用计数的指针
};
智能指针循环引用
循环引用是指使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环的情况,有点类似于死锁的情况,这种情况下,智能指针往往不能正常调用对象的析构函数,从而造成内存泄漏。举个例子:
#include <iostream>
using namespace std;
template <typename T>
class Node
{
public:
Node(const T& value)
:_pPre(NULL)
, _pNext(NULL)
, _value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
cout << "this:" << this << endl;
}
shared_ptr<Node<T>> _pPre;
shared_ptr<Node<T>> _pNext;
T _value;
};
void Funtest()
{
shared_ptr<Node<int>> sp1(new Node<int>(1));
shared_ptr<Node<int>> sp2(new Node<int>(2));
cout << "sp1.use_count:" << sp1.use_count() << endl;
cout << "sp2.use_count:" << sp2.use_count() << endl;
sp1->_pNext = sp2; //sp2的引用+1
sp2->_pPre = sp1; //sp1的引用+1
cout << "sp1.use_count:" << sp1.use_count() << endl;
cout << "sp2.use_count:" << sp2.use_count() << endl;
}
int main()
{
Funtest();
system("pause");
return 0;
}
//输出结果
//Node()
//Node()
//sp1.use_count:1
//sp2.use_count:1
//sp1.use_count:2
//sp2.use_count:2
从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。