智能指针

目录

智能指针的原理:

C++98提供了auto_ptr的智能指针

c++11提供更靠谱的unique_ptr

C++11提供更靠谱的并且支持拷贝的shared_ptr

std::shared_ptr的线程安全问题

循环引用

C++11和boost中智能指针的关系

守卫锁


智能指针是一种预防型的内存泄漏的解决方案。智能指针在C++没有垃圾回收器的环境下,可以很好的解决异常安全等带来的内存泄露问题。

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

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此没玩们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显示的释放资源(例如锁)
  • 对象所需的资源在其生命周期内始终保持有效。

智能指针的原理:

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

C++98提供了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;
};

auto_ptr缺点:拷贝后会把原来对象的指针赋空了,导致ap对象悬空,通过原来的对象访问资源时就会出现问题。

c++11提供更靠谱的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;
};

C++11提供更靠谱的并且支持拷贝的shared_ptr

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

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

template <class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr = nullptr)
		: _ptr(ptr), 
		_pRefCount(new int(1)),
		_pMutex(new mutex)
	{        
		// 如果是一个空指针对象,则引用计数给0
		if (_ptr == nullptr) 
			*_pRefCount = 0;
	}

	~SharedPtr() 
	{ 
		Release();
	}

	//拷贝
	SharedPtr(const SharedPtr<T>& sp) 
		: _ptr(sp._ptr), 
		_pRefCount(sp._pRefCount),
		_pMutex(sp._pMutex) 
	{        // 如果是一个空指针对象,则不加引用计数,否则才加引用计数
		if (_ptr)
			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;

			if (_ptr)
				AddRefCount();
		}

		return *this;
	}

	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }

	int UseCount() 
	{ 
		return *_pRefCount;
	}   
	T* Get()
	{ 
		return _ptr;
	}

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

		return *_pRefCount;
	}

	int SubRefCount() 
	{        
		// 加锁或者使用减1的原子操作
		_pMutex->lock();
		--(*_pRefCount);
		_pMutex->unlock();

		return *_pRefCount;
	}

private:    
	void Release()
	{        
		// 引用计数减1,如果减到0,则释放资源
		if (_ptr && SubRefCount() == 0)
		{            
			delete _ptr; 
			delete _pRefCount;
		}    
	} 
private:    
	int* _pRefCount; // 引用计数
	T*   _ptr;       // 指向管理资源的指针
	mutex* _pMutex;      // 互斥锁 
};

std::shared_ptr的线程安全问题

  1. 智能指针对象中引用计数是多个智能指针对象共享的,由于++和--并不是原子的,因此对齐操作要加锁。
  2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
  3. 也就是说它本身是线程安全的,但是他管理的资源不是线程安全的。

循环引用

weak_ptr可以解决这个问题,weak_ptr支持把一个shared_ptr对象赋给weak_ptr对象(赋值和拷贝构造的参数写成shared_ptr,重载),

weak_ptr不会增加引用计数,weak_ptr,析构函数不释放资源,不是RAII,weak_ptr是shared_ptr的小保姆,收拾循环引用的,但是这种方式是被动的,需要程序员来写,它自己并不会自动来管理。

struct ListNode 
{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	//shared_ptr<ListNode> _prev;
	//shared_ptr<ListNode> _next;


	~ListNode() 
	{ 
		cout << "~ListNode()" << endl;
	}
};

int main() 
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node1->_next = node2;
	node2->_prev = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	//system("pause");
	return 0;
}
  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2 成员,所以这就叫循环引用,谁也不会释放。

 不是new出来的对象,shared_ptr设计了一个删除器来解决这个问题。

假设对象是malloc出来的,构造函数还可以传一个删除器的东西,一个仿函数。

template<class T>
struct FreeFunc {    
    void operator()(T* ptr)
    {        
        cout << "free:" << ptr << endl;
        free(ptr);    
    }
};
 
template<class T> 
struct DeleteArrayFunc {    
    void operator()(T* ptr)
    {
         cout << "delete[]" << ptr << endl;
         delete[] ptr;
    }
};
 
int main() 
{    
    FreeFunc<int> freeFunc;
    shared_ptr<int> sp1((int*)malloc(4), freeFunc);
 
    DeleteArrayFunc<int> deleteArrayFunc;
    shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
       
    return 0;
}

C++11和boost中智能指针的关系

boost是C++的一个非官方的第三方库,产生了很多有用的东西

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的 scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

守卫锁

RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题。加锁到解锁中间抛异常,break,return

 设计出一个类,构造函数的时候,就锁住,析构函数里解锁

#include <thread> 
#include <mutex>
#include <iostream>
using namespace std;
// C++11的库中也有一个lock_guard,下面的LockGuard造轮子其实就是为了学习他的原理 
template<class Mutex>
 class LockGuard
 { 
public:
    LockGuard(Mutex& mtx)
         :_mutex(mtx)
    { 
       _mutex.lock();
    }
 
    ~LockGuard()
    {
        _mutex.unlock();
    }
 
    LockGuard(const LockGuard<Mutex>&) = delete;
 
private:
    // 注意这里必须使用引用,否则锁的就不是一个互斥量对象
    Mutex& _mutex; 
};
 
mutex mtx; 
static int n = 0;
 
void Func()
{    
    for (size_t i = 0; i < 1000000; ++i)
    {        
        LockGuard<mutex> lock(mtx);
        ++n;    
    } 
}
 
int main() 
{    
int begin = clock();
    thread  t1(Func);
    thread  t2(Func);
 
    t1.join();
    t2.join();
 
    int end = clock();
 
    cout << n << endl;
    cout <<"cost time:" <<end - begin << endl;
    return 0;
 }

如果类内的锁不是引用,构造函数锁的才是传过来的锁。

只锁某一行,给一个{ }的局部域就好了