C++ 面试常考的智能指针原理

智能指针 利用对象生命周期来控制程序资源

在对象构造时获取资源,接着控制对资源的访问,使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源

所以指针只能看似来像指针,其实是一个类,把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
(1) 不需要用户显式地释放资源
(2) 采用这种方式,对象所需的资源在其生命期内始终保持有效

C++ 常用的智能指针

(1) auto_ptr 是 C++98 版的,但是被 C++11 淘汰了,是因为使用它的危险很大,采用的所有权模式,当一个指针拷贝给另一个指针时,自己的权限会发生转移,所以存在内存崩溃的风险

(2) unique_ptr 可以替换 auto_ptr 智能指针,实现独占式的概念, 保证同一时间只有一个智能指针可以指向该对象,如果在赋值的时候,编译器觉得该操作可能会发生错误,则编译不会通过,如果被赋值的是一个临时右值,则编译器允许这样做

例:

unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1;   // #1 编译器不允许
unique_ptr<string> pu3;
// #2 编译器允许以下操作
pu3 = unique_ptr<string>(new string ("You"));   

简单模拟 unique_ptr 智能指针

// 模拟实现一份简答的UniquePtr,了解原理
template<class T>
class UniquePtr{
public:
	UniquePtr(T * ptr = nullptr)
		: _ptr(ptr)
	{}
	~UniquePtr(){
		if (_ptr)
			delete _ptr;
	}
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	// 方法一:C++98防拷贝的方式:只声明不实现 + 声明成私有
	UniquePtr(UniquePtr<T> const &);
	UniquePtr & operator=(UniquePtr<T> const &);
	
	// 方法二:C++11防拷贝的方式:delete
	UniquePtr(UniquePtr<T> const &) = delete;
	UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
	T * _ptr;
};

(3) shared_ptr 通过 引用计数 的方式表明资源被几个指针使用,来实现内存资源共享,当某个对象销毁的时候,就将对应的资源计数减一,如果计数为 0,就释放该资源。如果计数不为0就贸然的释放了资源,那么其他使用该资源的对象就成了野指针!

它有两个缺陷 :(1) 线程不安全 (2)循环引用

当多个对象去使用同一份资源的时候必然会造成线程不安全
而引用计数的操作不是原子操作,所以可能会导致资源未释放或程序奔溃问题,所以需要在进行计数的时候加锁。

虽然说 shared_ptr 加锁后解决了指针独占的问题,而且引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象,会造成循环引用,导致析构函数无法被调用造成内存泄露。

我们首先解释解释,啥是循环引用?

两个智能指针相互指向,导致最后的对象资源的计数不能减为 0 而无法调用析构函数,那么内存就会泄漏

例如:

#include <iostream>
#include <stdlib.h>
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->_pPre = sp1;

	cout << "sp1.use_count:" << sp1.use_count() << endl;
	cout << "sp2.use_count:" << sp2.use_count() << endl;
}

运行结果没有调用析构函数,而且资源也没有释放
在这里插入图片描述
那么应该怎么解决这个问题?

利用 weak_ptr 智能指针,在进行指向的时候不会对资源进行计数,也就是说他的构造和析构不会引起引用计数的增加或者减少,如果说两个 shared_ptr 相互引用,那么这两个指针的计数永远都不可能变为0,资源永远都不会释放

注意:这种方法仅限于编译的时候,意思就是当你能预见编译器期间会出现内存泄漏的情况,你再去使用它可以解决这个循环引用的问题,但是如果在运行阶段出现了循环引用,还是会内存泄漏的。

template <typename T>
class Node{
public:
	Node(const T& value)
		: _value(value)
	{
		cout << "Node()" << endl;
	}

	~Node(){
		cout << "~Node()" << endl;
		cout << "this:" << this << endl;
	}

	weak_ptr<Node<T>> _pPre;
	weak_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->_pPre = sp1;

	cout << "sp1.use_count:" << sp1.use_count() << endl;
	cout << "sp2.use_count:" << sp2.use_count() << endl;
}

在这里插入图片描述


实现一个简单的智能指针

(1) 实现智能指针的 RAII 特性,在构造函数中接收用户资源,析构函数中释放资源,可以通过定制删除器的方式,或者仿函数

(2) 重载解引用操作和取值操作,使它具有像指针一样的行为.

// 定制删除器,这是一个仿函数
template<class T>
struct DeleteArrayFunc {
	void operator()(T* ptr){
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	~SmartPtr(){
		if (_ptr) {
			delete _ptr;
		}
	}
	// 重载指针操作
	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};

struct Date{
	int _year;
	int _month;
	int _day;
};

int main(){
	SmartPtr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;
	SmartPtr<Date> sparray(new Date);
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的温柔香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值