C++智能指针(3.30)

一、问题引入

关于C++中的new和delete操作符,

我们知道这两个操作符必须成对存在,才能避免内存泄漏。

这一点在学习的时候被认为是常识,然而,在实际编写代码的过程中,却常常很难做到。

下面有3种情况:

1、代码很长。

当需要用到delete的地方离使用与之对应的new操作符距离非常远时,我们很容易忘记delete。当然,这种情况是完全可以避免的。

2、如下面代码:

void Test()
{
	int *pi = new int(1);
	if(1)
	{
		return;
	}
	delete pi;     //程序并没有执行到这一步
}
int main()
{
	void Test();
	return 0;
}

这里我们在Test函数中,开辟了一段长度为 1个int类型大小 的动态内存,

但接下来,进入if语句,直接return掉了,因此之前开辟的那段内存没有得到回收,导致内存泄漏。

对于这种情况,我们可以这样修改:

void Test()
{
	int *pi = new int(1);
	if(1)
	{
		delete pi;
		return;
	}
	delete pi;    //程序并没有执行到这一步
}
int main()
{
	void Test();
	return 0;
}

3、让情况再复杂一些,看看这段代码:

void DoSomeThing()
{
	if(1)
	{
		throw 1;
	}
}
void Test()
{
	int *pi = new int(1);
	DoSomeThing();
	delete pi;
}
int main()
{
	try
	{
		Test1();
	}
	catch(...)
	{
		;
	}
	return 0;
}

在Test()函数中,看似new和delete是成对存在的,中间也只有一行代码,

然而当中间这个DoSomeThing()抛出异常导致这个程序结构不是按部就班地进行时,

仍然没有执行delete pi 这一步。

这种情况我们仍然可以做如下修正:

void DoSomeThing()
{
	if(1)
	{
		throw 1;
	}
}
void Test()
{
	int *pi = new int(1);
	try
	{
		DoSomeThing();
	}
	catch(...)
	{
		delete pi;
		throw;
	}
	delete pi;
}
int main()
{
	try
	{
		Test1();
	}
	catch(...)
	{
		;
	}
	return 0;
}

在Test中加上一个try catch语句,作为DoSomeThing()函数抛出异常的“中介”,处理动态内存。

以上3种情况都是完全可以解决的。

但特别是当程序是类似情况3的结构,甚至更复杂的时候,我们不得不加上一大堆的代码,却仅仅是为了处理动态内存的回收。

这是十分影响开发效率的,同时程序也变得难以阅读。

二、简单的智能指针

我们知道,类的成员函数中,析构函数的存在似乎能解决动态内存回收的问题:当程序出了类的作用域,会自动调用该类的析构函数。

带着这个思想,我们定义一个名为AutoPtr的类模板

template<typename T>
class AutoPtr
{
public:
	AutoPtr(T *ptr = NULL)
		:_ptr(ptr)
	{}
	AutoPtr(AutoPtr<T> &ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = NULL;
	}
	~AutoPtr()
	{
		if (_ptr != NULL)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			_ptr = NULL;
		}
	}
	AutoPtr<T> operator=(AutoPtr<T> &ap)
	{
		if(this != &ap)
		{
			if (_ptr != ap._ptr)
			{
				delete _ptr;
			}
			_ptr = ap._ptr;
			ap._ptr = NULL;
		}
		return *this;
	}
	T &operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
protected:
    T* _ptr;

值得注意的是,当这个模板类的模板类型为结构体或类时,我们需要访问该类的成员,因此需要一个

"->"操作符的重载,举个例子:

已知结构体stru的定义如下:

struct stru
{
	void PrintTest()
	{
		std::cout << "hi" << std::endl;
	}
};

对于这样的类,当我们执行如下代码

	stru st1;
	stru *ps1 = &st1;
	AutoPtr<stru> aps1(ps1);
	aps1->PrintTest();//操作符的重载

最后一行发生 "->"操作符的重载,但根据之前关于 "->"操作符的定义严格意义上,访问到的是

_ps1; 这行代码实际上经过重载后,应该为:_ps1PrintTest()

其实这里是编译器为了保证代码的可读性,把_ps1PrintTest优化为 _ps1->PrintTest()。

(这个优化在g++和VS2015中都是存在的)

当然,这个智能指针并不完美,仔细观察我的拷贝构造函数和赋值操作符的重载,你会发现,每当我们进行拷贝或者赋值的时候,永远都是让源智能指针置空,也就是说,同一时间一段动态内存只能由一个智能指针来维护。

这样做是有原因的:如果有若干个个智能指针指向同一块动态空间,那么在析构的时候,将会对这块空间析构若干次,程序必然崩溃。所以我只能让这个类具有“同一时间一段动态内存只能由一个智能指针来维护”的特性。

总之,尽管不完美,但我们还是实现类一个简单的智能指针AutoPtr。有了它,我们可以不用手动delete,将释放内存的工作全部交给析构函数来处理。

上面说的“不完美”主要体现为以下几点,也是我接下来要解决的问题:

1、“同一时间一段动态内存只能由一个智能指针来维护”的特性:这个特性让它和普通的指针“不太像”,不符合普遍的编程习惯,

此外如果手动构造多个指向同一内存的智能指针,仍会导致析构函数的时候对同一内存析构多次,仍会令程序崩溃,因此这样的做法并没有根本上解决问题;

2、智能指针只能指向动态内存,如果指向静态内存,在析构的过程中必然崩溃。

三、改进

我之前写的AutoPtr有种种不完美之处,需要对其进行一些改进,其中由于拷贝和赋值的不合理导致

同一时间在特定的一段动态内存,只能存在一个AutoPtr维护,针对这个问题,ScopedPtr 和 SharedPtr都是对AutoPtr的改进

ScopedPtr类模板的定义如下:

template<typename T>
class ScopedPtr
{
public:
	ScopedPtr(T* ptr = NULL)
		:_ptr(ptr)
	{}

	~ScopedPtr()
	{
		if (_ptr)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
			_ptr = NULL;
		}
	}
	T &operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
protected:
	ScopedPtr(ScopedPtr<T> &sp);
	ScopedPtr<T> &operator=(ScopedPtr<T> &sp);
protected:
	T* _ptr;
};

这里跟AutoPtr的区别在于

1:拷贝构造函数和赋值操作符重载函数只声明,没有定义

2:并且以上两个函数的声明放在了protected限定符内

采用这样的做法,当我们想对ScopedPtr类型的变量进行拷贝构造或者赋值时,由于并没有定义相应的函数,程序是无法编译通过的,因此根本上就防止了赋值行为的发生;

此外,将这两个函数放在protected限定符内也是有意义的:假如我们声明这两个函数为public,那么的确可以起到同样防止调用的效果。

但是一旦这样做,造成的后果就是,其他人在读这样的代码时,有可能会误解为我们没来得及定义这两个函数,然后“画蛇添足”地加上相应的定义,如此一来,这个ScopedPrt类就与之前我们定义的AutoPtr类没两样了。

此外,心怀恶意的“捣乱者”一旦看到这样的漏洞,破坏掉你的程序也将会轻而易举——只需要给这两个函数写上定义就可以了。

总之,将这两个函数声明为protected是有意义的,它可以防止其他人对程序的破坏行为。

(待续)

时间: 2024-08-11 21:52:26

C++智能指针(3.30)的相关文章

C++智能指针剖析(下)boost::shared_ptr&amp;其他

1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的boost::shared_ptr. boost::shared_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使

使用智能指针来管理对象 (基于RAII)

////一个简单的防止内存泄露的例子//void test() { //使用RAII的特性管理资源 //当智能指针unique_ptr被销毁时,它指向的对象也将被销毁 //这里test函数返回后 p将自动销毁 //unique_ptr<int[]> p( new int[200] ); //直接生成资源 //test函数返回后 p不能被正常销毁,就会造成资源泄露 //int* p = new int[200]; } int main() { while( 1 ) { test(); Sleep

STL模板_智能指针概念

一.智能指针1.类类型对象,在其内部封装了一个普通指针.当智能指针对象因离开作用域而被析构时,其析构函数被执行,通过其内部封装的普通指针,销毁该指针的目标对象,避免内存泄露.2.为了表现出和普通指针一致的外观和行为,重载了解引用运算符(*)和间接成员访问运算符(->)函数,令其使用者可以将一个智能指针当成普通指针一样地使用.3.智能指针没有拷贝语义,只有转移语义,任何时候都只有一个智能指针对象持有真正的对象地址.4.智能指针不支持对象数组.二.模板的非类型参数1.无论是函数模板还是类模板,其模板

C++——几种简单的智能指针

最简单的智能指针就是将指针封装在类里,同时将该类的复制与赋值禁用,也就是使该类失去值语义. 实现代码如下: 1 #ifndef SMART_PTR_H 2 #define SMART_PTR_H 3 #include <iostream> 4 5 6 template <typename T> 7 class SmartPtr 8 { 9 public: 10 typedef T value_type; 11 typedef T* pointer; 12 typedef T&

引用内部函数绑定机制,R转义字符,C++引用,别名,模板元,宏,断言,C++多线程,C++智能指针

 1.引用内部函数绑定机制 #include<iostream> #include<functional> usingnamespacestd; usingnamespacestd::placeholders; //仿函数.创建一个函数指针,引用一个结构体内部或者一个类内部的共同拥有函数 structMyStruct { voidadd(inta) { cout <<a <<endl; } voidadd2(inta,intb) { cout <&

实战c++中的智能指针unique_ptr系列-- unique_ptr的operator=、operator bool、reset、swap、get等介绍

既然打算把unique_ptr写一个系列,就要详尽一点,有些内容也许在vector的时候有个涉及,但是现在还是再谈论一番. 我们要把unique_ptr看做一个类,废话了,它当然是一个类.所以这个类肯定也重载了赋值运算符,即operator=.现在就开始看看operator=在unique_ptr中的使用: 官方描述如下: move assignment (1) unique_ptr& operator= (unique_ptr&& x) noexcept; assign null

(转)剖析C++标准库智能指针(std::auto_ptr)

不可否认,资源泄露(resource leak)曾经是C++程序的一大噩梦.垃圾回收 机制(Garbage Collection)一时颇受注目.然而垃圾自动回收机制并不能 满足内存管理的即时性和可视性,往往使高傲的程序设计者感到不自在. 况且,C++实现没有引入这种机制.在探索中,C++程序员创造了锋利的 "Smart Pointer".一定程度上,解决了资源泄露问题. 也许,经常的,你会写这样的代码: //x拟为class: // class x{ // public: // int

C++11中智能指针的原理、使用、实现

目录 理解智能指针的原理 智能指针的使用 智能指针的设计和实现 1.智能指针的作用 C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理.程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存.使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存. 理解智能指针需要从下面三个层次: 从较浅的层面看,智能指针是利用了一种叫做RAII(资

再谈智能指针

http://www.cnblogs.com/lewiskyo/p/4214592.html  之前写过一篇文章介绍智能指针,并且简单实现了auto_ptr. 里面提到 auto_ptr 不能做为Stl容器的元素,原因具体是 http://www.360doc.com/content/14/0429/16/1317564_373230218.shtml 简要来说就是在对Stl容器元素进行复制构造时,不能改变原来的值(const T2& value),而auto_ptr在复制构造的时候,一定会修改