智能指针

本文主要讨论C++标准库中的auto_ptr,boost库中的scoped_ptr, shared_ptr, weak_ptr

1.什么是RAII

       RAII全称为Resourse Acquisition Is Initialization,意为资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成自愿的清理,可以保证资源的正确初始化和释放。

       RAII是一些面向对象语言中的一种惯用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有应用。1984-1989年期间,⽐雅尼·斯特劳斯特鲁普和安德鲁·柯尼希在设计C++异常时, 为解决资源管理时的异常安全性⽽使⽤了该⽤法,后来⽐雅尼·斯特劳斯特鲁 普将其称为RAII。 

       RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配,同时由析构函数完成资源的释放。在这种要求下,只要对象能正确的析构,就不会出现资源泄露的问题。

2.什么是智能指针

智能指针不是指针,它是类似于有着指针功能的类,这个类可以智能(自动化)的管理指针所指向的动态资源的释放。它是为了解决抛异常执行流跳转,导致内存泄露的问题。

智能指针的原理:

智能指针的基本原理就是使用对象去管理指针,delete的操作利用析构函数执行,而析构函数的调用是根据这个对象的作用域来确定的,离开了作用域,析构函数被调用。delete的操作也将被执行。

为什么需要智能指针?

       一般而言,使用new或malloc申请了内存,再使用delete或free释放掉就好了,为什么还需要智能指针?简单的逻辑代码之中,程序员可以看见申请内存,意识到释放内存,当存在多次调用申请的内存,或者工程代码庞大,由多个人员完成,开辟了很多内存,那么你怎么知道他开辟了多少内存,在哪里释放内存,释放完全了吗?在代码中经常会忘掉释放动态开辟的资源,尽管小心翼翼,但还是防不胜防,一不小心就内存泄露了。╮(╯▽╰)╭

       这时候,就需要智能指针了,它可以帮助我们管理动态分配的内存,适当的时候。释放掉内存,再也不用担心内存泄露了。(*^▽^*)

下面的例子中,如果出现异常,会在catch中进行释放内存。没有出现异常,最后会释放到内存,但容易出错的时,有是会忘掉在最后释放内存。

void Test()
{
    int* p = new int(2);
    
    //...
    try{
        DoSomeThing();
    }
    catch(...)
    {
        delete p;
        throw;
    }
    //...
    delete p;
}

智能指针释放内存的时机是什么时候?

对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放由它管理的堆内存。所有智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。 


3.几种常见的智能指针

3.1 C++标准库之std::auto_ptr:

头文件位于<memory>,auto_ptr的原理是管理权的转移,建立不要使用。

在cplusplus下auto_ptr的函数有:

auto_ptr的使用:

#include <iostream>
#include <memory>

int main () {
  std::auto_ptr<int> p1 (new int);
  *p1.get()=10; //*(p1.get()),赋值

  std::auto_ptr<int> p2 (p1); //p1将管理权限转移给p2

  std::cout << "p2 points to " << *p2 << '\n';

  return 0;
}

模拟实现:

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr)
		:_ptr(ptr)
		,_owner(true)
	{}
	
	AutoPtr(AutoPtr<T>& op)
		:_ptr(op._ptr)
		,_owner(op._owner)
	{
		//这是管理权的转移,当通过拷贝构造,将op赋值给新的AutoPtr时,op同时
		//失去了对指针指向的管理权。只有最后一个拷贝的拥有管理权。
		op._owner = false;
		op._ptr = NULL;
	}
	
	//为了让AutoPtr更像原生指针,对它进行->和*的操作符重载。
	T* operator->()
	{
		return _ptr;
	}
	
	T& operator*()
	{
		return *_ptr;
	}
	
	T* get()
	{
		return _ptr;
	}
	
	T* release()
	{
		T* tmp = _ptr;
		delete _ptr;
		return tmp;
	}
	
	AutoPtr<T>& operator=(AutoPtr<T>& a)
	{
		if(this != &a)
		{
			if(_ptr)
				delete _ptr;
			_ptr = a._ptr;
			_owner = a._owner;
			a._ptr = NULL;
			a._owner = false;
		}
	}
	
	//将auto_ptr的指向释放掉,并设置一个新值。
	//使用operator delete进行释放内存,其实只是断开了指针和内存之间的指向关系,
	//内存本身还在。
	void reset(T* p=0)
	{
		if(_ptr)
		{
			delete _ptr;
			*_ptr = *p;
		}	
	}
	
	~AutoPtr()
	{
		if(_owner)
			delete _ptr;
	}
private:
	T* _ptr;
	bool _owner;
};

普通的指针和智能指针的区别:

//auto_ptr:
auto_ptr<int> p1(new int);
//出了p1的作用域之后,p1会调用析构函数进行释放。


//普通指针
int* p2 = new int(2);
//...
delete p2;

p2是一个普通指针,当p2的生命周期结束的时候,为指针p2分配的内存(栈上)会被释放,但p2指向的内存空间(使用new在堆上动态开辟的空间)不会被释放,因此要手动的调用delete,释放掉在堆上动态开辟的内存空间。

p1是一个智能指针,是一个对象,当它的生命周期结束的时候,会自动的调用它的析构函数,在析构函数中会释放掉动态分配的内存(在堆上)。

问题1:会出现越界的问题,使用auto_ptr时,当越界发生在代码区,常量区时,会进行报错。当在其他区段时不一定会报错,因为检查越界是在标志位处。而auto_ptr的_owner会将错误延迟。

问题2:会造成野指针问题,一个空间的多个对象,只有一个对象拥有管理权,谁最后被赋值,谁拥有管理权,拥有管理权的才能析构。当访问没有管理权的对象时,就会造成野指针的问题。

auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2;
p2 = p1; //p1将管理权转移给p2
cout<<*p2<<endl;
cout<<*p1<<endl;

上面的程序在运行时会出错,出错的原因是,赋值语句p2 = p1将管理权从p1转到p2,这导致p1不再指向该字符串。auto_ptr类型的对象在放弃指向内存的所有权之后,再用它来访问该对内存,就会出错,才是这个智能指针是空的。


以下介绍boost库中的三种智能指针scoped_ptr,  shared_ptr,  weak_ptr;在C++11中,根据boost中的智能指针,引入了三个智能指针unique_ptr, shared_ptr,  weak_ptr,其功能和boost库中的差不多。

3.2 boost库之scoped_ptr

使用boost库中的智能指针,需要引入boost库的头文件。

boost库是一个第三方库,需要在网上进行下载。随后要进行程序编译,配置环境变量(为了写头文件的时候,不加绝对路径,不配置也可以)。还要在VS下进行一个配置。

boost安装见博客:https://blog.csdn.net/rankun1/article/details/53258799

附上一个下载好的boost库:

链接:https://pan.baidu.com/s/1AgHwPFl--T5G1SgjyE4q0g 密码:2fib

scoped_ptr是一个守卫指针,简单粗暴,就是防拷贝。scoped_ptr与auto_ptr都是采用管理权转移,如果用scoped_ptr代替auto_ptr,访问没有管理权限的内存也是会出错的。只是使用unique_ptr时,程序不会等到运行期崩溃,而在编译器发现赋值操作p2 = p1时,就会报错,因为scoped_ptr防拷贝。

如何进行防拷贝呢?

防拷贝就是让别人不能复制。如何实现防拷贝:

       1.只声明,不实现

       2.声明成私有成员

template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr)
		:_ptr(ptr)
	{}
private:
	ScopedPtr(const ScopedPtr<T>& x);
	ScopedPtr<T>& operator=(const ScopedPtr<T>& x);
	
	T* _ptr;
};

3.3 boost库之shared_ptr

shared_ptr功能齐全,原理是引用计数,强引用,解决了auto_ptr管理权转移造成的问题。

在auto_ptr中,当管理权转移之后,在访问之前的对象就会出错,之前的对象已经为空。在shared_ptr中,当p2 = p1时,p1和p2指向同一个内存,这块内存的引用计数会+1。在p2的生命周期结束时,会调用它的析构函数,此时引用计数-1。当p1的生命周期结束的时候,引用计数为1,调用它的析构函数,此时引用计数为0,这时才真正的释放掉了为对象 分配的内存。

模拟实现:

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr=NULL)
		:_ptr(ptr)
		,_refCount(new int(1))
	{}
	
	SharedPtr(const SharedPtr<T>& sp)
	{
		_ptr =sp._ptr;
		_refCount = sp._refCount;
		++(*_refCount);
	}
	
	//传统写法
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if(_ptr != sp._ptr)
		{
			if(--(*_refCount) == 0)
			{
				delete _ptr;
				delete _refCount;
			}
			_ptr = sp._ptr;
			_refCount = sp._refCount;
			++(*_refCount);
		}
		return *this;
	}
	
	//现代写法--非常的神奇!!!后面讲
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		swap(_ptr,sp._ptr);
		swap(_refCount,sp._refCount);
		return *this;
	}
private:
	T* _ptr;
	int* _refCount;
};

赋值运算符重载的现代写法问题:

3.4 boost库之weak_ptr

什么是weak_ptr?

weak_ptr是为了解决shared_ptr循环引用的缺陷问题。一般和shared_ptr搭配使用,weak_ptr不增加引用计数,不能单独使用。

什么是循环引用?

循环引用就是相互引用,相互依赖,导致谁都不能释放。

图解:

代码解释:

//循环引用
class B;
class A
{
	A(shared_ptr<B> ptr)
	:_a(ptr)
	{}
	
	~A()
	{
		std::cout << "~A" <<std::endl;
	}
	
private:
	shared_ptr<B> _a; 
};

class B
{
	B(shared_ptr<A> ptr)
	:_b(ptr)
	{}
	
	~B()
	{
		std::cout << "~B" << std::endl;
	}
private:
	shared_ptr<A> _b;
};

对象A的释放依赖于对象B的释放,对象B的释放依赖于对象A的释放,最终谁也释放不了。

模拟实现:

template<class T>
class WeakPtr
{
public:
	WeakPtr()
	:_ptr(NULL)
	{}
	
	WeakPtr(const SharedPtr<T>& sp)
	:_ptr(sp.GetPtr())
	{}
	
	T& operator*()
	{
		return *_ptr;
	}
	
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

weak_ptr的用法:

1. weak_ptr与shared_ptr配合使用,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
2. 使用weak_ptr的成员函数use_count(),可以观测资源的引用计数。
3. 使用weak_ptr的成员函数expired(),可以判断引用计数是否为0。如果引用计数为0,返回true;否则返回false。
4. 使用weak_ptr一个非常重要的成员函数lock(),从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

shared_ptr<int> sp1(new int);
weak_ptr<int> wp(sp1);
if (!wp.expired()){
    shared_ptr<int> sp2 = wp.lock();
    *sp2 = 2;
}
cout<<*sp2<<endl;

3.5 C++11之unique_ptr , shared_ptr , weak_ptr

C++11的智能指针是根据boost库发展来的。有如下用法对应关系:C++11  ---- boost

unique-ptr  ------   scoped_ptr            shared_ptr  ------  shared_ptr           weak_ptr  --------   weak_ptr

(*^▽^*)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值