智能指针

RAII

RAII是一种利用对象生命周期来控制程序资源的简单技术

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

  • 不需要显式地释放资源
  • 采用这种方式, 对象所需的资源在其生命周期内始终保持有效

智能指针的原理

  • RAII特性
  • 重载operator*和operator->, 具有像指针一样的行为

std:auto_ptr

C++98提供了auto_ptr

// 例
#include <memory>

class Date {
public:
	Date() {cout << "Date()" << endl;}
	~Date() {cout << "~Date()" << endl;}
	
	int _year;
	int _month;
	int _day;
};

int main() {
	auto_ptr<Date> ap(new Date);
	auto_ptr<Date> copy(ap);
	// auto_ptr存在的问题: 当对象拷贝或赋值后, 前面的对象就悬空了
	// ap->_year = 2019;
	return 0;
}

auto_ptr的实现原理: 管理权转移的思想

// 模拟实现auto_ptr
template <class T>
class AutoPtr {
public:
	AutoPtr(T *ptr = NULL)
		: _ptr(ptr)
	{}
	
	~AutoPtr() {
		if(_ptr) {
			delete _ptr;
		}
	}
	
	// 一旦发生拷贝, 就将ap中的资源转移到当前对象中, 然后另ap与其所管理的资源断开联系
	// 这样就解决了一块空间被多个对象使用所造成的程序崩溃问题
	AutoPtr(AutoPtr<T>& ap) 
		: _ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}
	
	AutoPtr<T>& operator = (AutoPtr<T>& ap) {
		// 检测是否为自己给自己赋值
		if(this != &ap) {
			// 释放当前对象中的资源
			if(_ptr) {
				delete _ptr;
			}
			// 转移ap资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	
	T& operator*() {return *_ptr;}
	T *operator->() {return _ptr;}
private:
	T *_ptr;
};

int main() {
	AutoPtr<Date> ap(new Date);
	// 这里拷贝后把ap对象的指针赋空了, 导致ap对象悬空
	// 通过ap对象访问资源就会出现问题
	AutoPtr<Date> copy(ap);
	// ap->_year = 2019;
	return 0;
}

std::unique_ptr

C++11提供了std::unique_ptr

// 例
#include <memory>

class Date {
public:
	Date() {cout << "Date()" << endl;}
	~Date() {cout << "~Date()" << endl;}
	
	int _year;
	int _month;
	int _day;
};

int main() {
	unique_ptr<Date> up(new Date);
	
	// unique_ptr设计思路是不让拷贝和赋值
	unique_ptr<Date> copy(up);
	return 0;
}

unique_ptr的实现原理: 简单粗暴的防拷贝

// 模拟实现unique_ptr
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;
};

std::shared_ptr

C++11提供了支持拷贝的shared_ptr

// 例
#include <memory>

class Date {
public:
	Date() {cout << "Date()" << endl;}
	~Date() {cout << "~Date()" << endl;}
	
	int _year;
	int _month;
	int _day;
};

int main() {
	// shared_ptr通过引用计数支持智能指针的拷贝
	shared_ptr<Date> sp(new Date);
	shared_ptr<Date> copy(sp);
	
	cout << "ref count:" << sp.use_count() << endl;
 	cout << "ref count:" << copy.use_count() << endl;
 	return 0;
}

shared_ptr的原理是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  • shared_ptr在其内部给每个资源都维护着一份计数, 用来记录该资源被几个对象共享
  • 在对象被销毁时(析构函数调用), 就说明自己不使用该资源了, 对象的引用计数减一
  • 如果引用计数为0, 说明自己是最后一个使用该资源的对象, 必须释放该资源
  • 如果不是0, 就说明除了自己还有其他的对象在使用该资源, 就不能释放该资源, 否则其他对象就成了野指针
// std::shared_ptr的模拟实现
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

template <class T>
class SharedPtr {
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr)
		, _pRefCount(new int(1))
		, _pMutex(new mutex)
	{}

	~SharedPtr() {
		Release();
	}

	SharedPtr(const SharedPtr<T>& sp)
		: _ptr(sp._ptr)
		, _pRefCount(sp._pRefCount)
		, _pMutex(sp._pMutex)
	{
		AddRefCount();
	}

	// sp1 = sp2
	SharedPtr<T>& operator = (const SharedPtr<T>& sp) {
		// if (this != &sp)
		if (_ptr != sp._ptr) {
			// 释放管理的旧资源
			Release();

			// 共享管理新对象的资源,并增加引用计数
			_ptr = sp._ptr;
			_pRefCount = sp._pRefCount;
			_pMutex = sp._pMutex;

			AddRefCount();
		}

		return *this;
	}

	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	int UseCount() { return *_pRefCount; }
	T* Get() { return _ptr; }

	void AddRefCount() {
		// 加锁或者使用加1的原子操作
		_pMutex->lock();
		++(*_pRefCount);
		_pMutex->unlock();
	}

private:
	void Release() {
		bool deleteflag = false;
		// 引用计数减1,如果减到0,则释放资源
		_pMutex->lock();
		if (--(*_pRefCount) == 0) {
			delete _ptr;
			delete _pRefCount;
			deleteflag = true;
		}
		_pMutex->unlock();

		if (deleteflag == true) {
			delete _pMutex;
		}
	}

private:
	int* _pRefCount; // 引用计数
	T* _ptr; // 指向管理资源的指针 
	mutex* _pMutex; // 互斥锁
};

int main() {

	SharedPtr<int> sp1(new int(10));
	SharedPtr<int> sp2(sp1);
	*sp2 = 20;
	printf("%d\n", sp1.UseCount());
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	SharedPtr<int> sp3(new int(10));
	sp2 = sp3;
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	cout << sp3.UseCount() << endl;
	sp1 = sp3;
	cout << sp1.UseCount() << endl;
	cout << sp2.UseCount() << endl;
	cout << sp3.UseCount() << endl;
	system("pause");
	return 0;
}

 

std::shared_ptr的线程安全问题

  • 智能指针对象中引用计数是多个智能指针对象共享的, 两个线程中智能指针的引用计数同时++或--, 这个操作不是原子的, 引用计数原来是1, ++了两次, 可能还是2, 这样引用计数就错乱了, 会导致资源未释放或程序崩溃的问题. 所以智能指针中引用计数++, --是需要加锁的
  • 智能指针管理的对象存放在堆上, 两个线程中同时去访问, 会导致线程安全问题

     


我们的征途是星辰大海!