C++11学习笔记(6) —— smart pointer: shared_ptr , auto_ptr

1. 简介

C++程序员最痛苦的莫过于对内存的管理,由于没有像C# 和 java的内存回收机制,C++程序员在复杂的程序中容易造成内存的泄露。即使程序员十分小心,异常的产生也有可能会造成部分内存的泄露(由于异常导致delete语句没有机会执行)。为了避免该问题,出现了各种类型的智能指针。只能指针实质就是重载了-> 和 * 操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针类的析构函数完成内存的释放。
旧版本C++为用户提供了 auto_ptr 智能指针,但由于其功能的限制以及自身的缺陷,完全没有发挥智能指针的强大功能,各种第三方的类库提供了远比C++标准库丰富的智能指针类。例如boost库,提供了 scoped_ptr, shared_ptr, weak_ptr等。 C++11 借鉴boost库,新引入了几个十分重要智能指针,shared_ptr, unique_ptr, weak_ptr。 丰富了标准库在智能指针方面的缺陷。

使用智能指针需要包含头文件

2. auto_ptr

2.1 定义

MSDN对 auto_ptr 的定义为:

The template class describes an object that stores a pointer to anallocated object of type Type * that ensures the object to which itpoints gets deleted when its enclosing auto_ptr gets destroyed.

2.2 一般用法

auto_ptr 对象将会绑定一个指定类型的指针,并且负责该指针资源的释放。auto_ptr 的基本使用方法如下所示:

struct MyStruct
{
	MyStruct(){cout<<"MyStruct()\n";}
	~MyStruct(){cout<<"~MyStruct()\n";}
	int i;
	int j;
};
auto_ptr pStruct(new MyStruct);
pStruct->i = 1;
pStruct->j = 2;
(*pStruct).i = 3;
(*pStruct).j = 4;

如上所示 

auto_ptrpStruct(new MyStruct);

定义了一个智能指针对象 pStruct, 该对象用于管理一个类型为 MyStruct 的指针。在auto_ptr类里,有一个指针成员变量,由该成员变量保存指针的地址。使用智能指针和使用一般的指针相同,因为其重载了 -> 操作符,例如

pStruct->i = 1;
pStruct->j = 2;

-> 操作符用于返回指针。智能指针类还重载了 * 操作符,用于返回对象的引用,例如

(*pStruct).i = 3;
(*pStruct).j = 4;

(*pStruct) 返回的是对象的引用,因此可以使用 (*pStruct).j 来访问该对象的成员。

2.3 资源管理

auto_ptr 智能管理从new 申请的一个对象资源,而不能管理动态分配的数组,例如:

struct MyStruct
{
	MyStruct(){cout<<"MyStruct()\n";}
	~MyStruct(){cout<<"~MyStruct()\n";}
	int i;
	int j;
};
	
{
	//compile OK, but we cannot use auto_ptr this way!
	auto_ptr pStructArr(new MyStruct[5]);
}

如上例所示了,为pStructArr绑定了一个动态分配的数组,编译器在编译阶段不会报错,但是由于auto_ptr内部维护的指针资源只能管理一个对象,在释放资源时使用的是delete而不是 delete[], 因此这种使用方式会导致资源的泄露。切记: auto_ptr只能用来管理一个对象的指针。

2.4 赋值和复制

使用auto_ptr 的复制和赋值要格外小心,对于普通的指针,进行复制(或赋值)时,实际是复制(或赋值)指针的地址,通过复制(或赋值),使得两个指针指向同一个地址。但是auto_ptr 却不同,在复制(或赋值)auto_ptr 对象之后,原来的auto_ptr 对象(右侧的对象)放弃资源的使用权,而新的auto_ptr 对象(左侧的)则拥有该基础对象的使用权。例如:

auto_ptr pInt(new int(3));
auto_ptr pInt2 = pInt;

首先pInt用于管理动态分配的整型指针,pInt2 = pInt进行复制后,pInt 被重置为未绑定的状态,而pInt2 此时拥有了对整型指针的管理权。需要注意的是,在复制或赋值时,如果pInt2 已经绑定了一个指针,那么会首先释放该指针的资源,然后再绑定新的资源。因此,auto_ptr的赋值或复制要格外小心。

2.5 测试和reset

当需要判断auto_ptr 对象是否绑定一个指针时,使用 get() 函数进行判断

auto_ptr pInt(new int(3));
if( pInt.get() )
	cout<< *pInt<< endl;

//error C2451: conditional expression of type 'std::auto_ptr<_Ty>' is illegal
if( pInt )
	cout<< *pInt << endl;

get() 用于返回 auto_ptr 管理的指针对象,因此判断是否绑定了指针要使用 obj.get() 的方式,auto_ptr 对象不能直接判断,如上例的 if( pInt ) 所示,编译器提示的错误如注释所示。

如果想要改变auto_ptr 管理的指针,使用 reset 函数,例如:

#include"stdafx.h"
#include
#include
#include
using namespace std;

int main()
{
	struct MyStruct
	{
		MyStruct(int ii) : i(ii) {cout<<"MyStruct() i="<< i << endl;}
		~MyStruct(){cout<<"~MyStruct()i="<< i << endl;;}
		int i;
	};

	auto_ptr pMyStruct(new MyStruct(5));
	pMyStruct.reset(new MyStruct(10));
	
	system("pause");
	return 0;
}

输出结果如下图所示:



由输出结果可以看出,

pMyStruct.reset(newMyStruct(10));

将auto_ptr对象重新绑定了新的指针。需要注意的,原绑定的资源在auto_ptr重新绑定资源后得到了释放!这就是 auto_ptr 的特性,一旦改变资源,原资源就会被释放。

 

根据auto_ptr的特点,在使用auto_ptr 时遵循如下的原则:

(1)不要使用auto_ptr 绑定指向静态分配对象的指针。

(2)不要使用两个auto_ptr 对象指向同一个对象。

(3)不要使用auto_ptr 对象保存指向动态分配数组的指针。

(4)不要将auto_ptr 对象存储在容器中,因为它不满足容易对存储对象赋值和复制的条件要求。

 

建议 auto_ptr 在函数体内动态申请局部变量的指针并且不需要进行指针的复制、赋值时使用。


3. shared_ptr

3.1 定义

CSDN 对 shared_ptr 的说明如下:

The template class describes an object that uses reference counting to manage resources. A shared_ptrobject effectively holds a pointer to the resource that it owns or holds a null pointer. A resource can be owned by more than one shared_ptr object; when the last shared_ptr object that owns a particular resource is destroyed, the resource is freed.

由定义可以看出,shared_ptr 使用引用计数的方式来实现对指针资源的管理。同一个指针资源,可以被多个 shared_ptr 对象所拥有,直到最后一个 shared_ptr 对象析构时才释放所管理的对象资源。

可以说,shared_ptr 是最智能的智能指针,因为其特点最接近原始的指针。不仅能够自由的赋值和拷贝,而且可以安全的用在标准容器中。


3.2 一般用法

shared_ptr 和 auto_ptr 一样,也是重载了 -> 和 * 操作符,使用 get() 函数可以得到原始的指针,auto_ptr 提供了隐式的bool类型转换,可以直接用于判断auto_ptr对象是否绑定了指针资源。 use_cout() 用于返回当前资源的引用计数。

#include"stdafx.h"
#include
#include
#include
using namespace std;

int main()
{
	struct MyStruct
	{
		MyStruct(int ii) : i(ii) {cout<<"MyStruct() i="<< i << endl;}
		~MyStruct(){cout<<"~MyStruct()i="<< i << endl;;}
		int i;
	};

	{

		//spMyStruct 维护一个MyStruct 类型的指针
		shared_ptr spMyStruct(new MyStruct(10)); 

		//与 auto_ptr 不同的是 shared_ptr 提供了隐式的bool类型转换,用于判断是否已绑定了资源
		if(spMyStruct)
		{
			cout << "1. Use count = " << spMyStruct.use_count() << endl;
		
			shared_ptr spMyStruct2 = spMyStruct;  //赋值
			cout<< "2. Use count = " << spMyStruct2.use_count() << endl;

			shared_ptr spMyStruct3(spMyStruct2);  //拷贝
			cout<< "3. Use count = " << spMyStruct3.use_count() << endl;

			spMyStruct2.reset();
			cout<< "4. After reset, use count = " << spMyStruct.use_count() << endl;


		}
		cout << "5. spMyStruct2 and spMystruct3 out of scope:" << endl;
		cout << "6. use count = " << spMyStruct.use_count() << endl;

		cout <<"7. spMyStruct out of scope:" << endl;
	}

	system("pause");
	return 0;
}

输出结果如下图所示:



由输出结果可以看出,对auto_ptr进行复制或者拷贝,实质是增添了引用计数, 和 auto_ptr 不同的是, reset() 函数在 shared_ptr 中的作用是对引用计数减1,并不会立即释放资源。知道引用计数减为0时,资源才得到真正的释放。


use_count()在实际的应用中很少使用,一般是用于调试,shared_ptr提供了一个高效的函数 unique(),同

use_count==1
等效,即,当维护的指针资源引用计数为1时,返回true.


3.3 类型转换


shared_ptr 的类型转换不能使用一般的static_cast,这种方式进行的转换会导致转换后的指针无法再被shared_ptr对象正确的管理。应该使用专门用于shared_ptr类型转换的 static_pointer_cast() , const_pointer_cast() 和 dynamic_pointer_cast().例如

#include"stdafx.h"
#include
#include
#include
using namespace std;

class BaseClass
{
public:
	BaseClass(){}
	~BaseClass(){}
public:
	virtual void Func() { cout<<"BaseClass::Func()\n";}
};

class DeriveClass : public BaseClass
{
public:
	DeriveClass(){}
	~DeriveClass(){}
public:
	virtual void Func() override { cout << "DeriveClass::Func()\n";}
};

int main()
{
	shared_ptr spBase( new BaseClass() );
	spBase->Func(); // 输出 BaseClass::Func()

	shared_ptr spDerive( new DeriveClass() );
	spDerive->Func();// 输出 DeriveClass::Func()

	shared_ptr spBase2 = dynamic_pointer_cast(spDerive);
	spBase2->Func();// 输出 DeriveClass::Func()

	system("pause");
	return 0;
}

3.4 make_shared

使用shared_ptr 避免了手动使用delete来释放由new 申请的资源,标准库也引入了make_shared函数来创建一个shared_ptr对象,使用shared_ptr 和 make_shared ,你的代码里就可以使 new 和 delete 消失,同时又不必担心内存的泄露。例如:

shared_ptr spInt = make_shared(10);

通过make_shared创建了一个整型变量的shared_ptr对象。至于资源的管理,就交给shared_ptr了!


3.5 总结

shared_ptr 是最为智能的智能指针,使用shared_ptr 和 make_shared,可以使你的C++代码几乎永远的和new, delete说再见! shared_ptr可以用在各种场合,局部变量,类的成员变量,参数,容器的元素!你不必担心资源的释放,不必担心内存的泄露!学会并熟练使用shared_ptr,提高开发效率吧。



你可能感兴趣的:(C++11)