c++几乎自动的管理内存实现

本文将来解决这样一个问题:复制一个对象的意义是什么?

假如一个对象的副本是一个完全不同的对象,这个副本具有原先对象的所有属性。然而,如果一个对象指向另一个对象的话,这个问题就变得更复杂:如果一个对象x指向一个对象y,那么复制x的时候是否也应该复制对象y呢?有时候这个问题很明显:如果y是x的一个成员,我们就必须在复制x的时候也复制y,如果x仅仅是一个指针,搞好指向y,那么我们就不需要复制y。

在这里我们定义了三种指针一样的类,他们在如何定义复制操作上互不相同。

一、一种管理内存的句柄类:复制所指向的对象,每个对象都复制一个副本

#ifndef _GUARD_14_HANDLE_H_   
#define _GUARD_14_HANDLE_H_
/********************************************************************  
创建时间:       2012/03/30 20:11 
文件名称:       14_Handle.h 
文件作者:       jimacs  
===================================================================== 
功能说明:       一个封装的句柄类,与它管理的对象的类型完全无关  
*********************************************************************/  
#include <iostream>
#include "core_grad.h"

template <class T>
class Handle
{
public:
	Handle():p(0) {} //默认构造函数
	Handle(const Handle& s):p(0) {
		if (s.p)
		{
			p=s.p->clone();
		}
	}
	Handle& operator= (const Handle& rhs)
	{
		if(&rhs!=this){
			delete p;
			p=rhs.p ? rhs.p->clone():0;
		}
		return *this;
	}
	~Handle() {delete p;}
	Handle(T* t):p(t) {} //单参数构造函数,与实际的对象绑定
	operator bool () const {return p;} //类型转化操作符
	T& operator*() const
	{
		if (p)
		{
			return *p;
		}
		throw runtime_error("unbond Handle");
	}	
	T* operator->() const{
		if (p)
		{
			return p;
		}
		throw runtime_error("unbone Handle");
	}
	//static bool compare_core_handles(const Handle& h1,const Handle& h2)
	//{
	//	return h1->name()<h2->name();
	//}
private:
	T* p;
};

////赋值操作符
//template <class T>
//Handle<T>& Handle<T>::operator =(const Handle& rhs)
//{
//	if(&rhs!=this){
//		delete p;
//		p=rhs.p ? rhs.p->clone():0;
//	}
//	return *this;
//}
//
////*
//template <class T>
//T& Handle<T>::operator * () const 
//{
//	if (p)
//	{
//		return *p;
//	}
//	throw runtime_error("unbond Handle");
//}	
//template <class T>
//T* Handle<T>::operator ->() const
//{
//	if (p)
//	{
//		return p;
//	}
//	throw runtime_error("unbone Handle");
//}


#endif

 

下面通过一个实例与一个学生信息类相结合,他们彼此独立,只是通过上面的句柄类来管理此类的指针。

#ifndef _GUARD_14_STUDENT_INFO_H_   
#define _GUARD_14_STUDENT_INFO_H_
/********************************************************************  
创建时间:       2012/03/31 9:58 
文件名称:       14_Student_info.h 
文件作者:       jimacs  
===================================================================== 
功能说明:       重新实现Student_info类,而handle作为纯粹的接口类,把管理
                指针的工作委托给Handle类来实现  
*********************************************************************/  
#include <iostream>
#include <stdexcept>

#include "14_Handle.h"
#include "core_grad.h"

class Student_info
{
public:
	Student_info() {}
	Student_info(std::istream& is) {read(is);}
	//这里没有个函数,不需要了,都交给Handle类来完成

	std::istream& read(std::istream&); //read
	std::string name() const
	{
		if (cp)
		{
			return cp->name();
		}
		else
		{
			throw std::runtime_error("uninitialized student");
		}
	}
	double grade() const
	{
		if (cp)
		{
			return cp->grade();
		}
		else
		{
			throw std::runtime_error("uninitialized student");
		}
	}
	static bool compare(const Student_info& s1,const Student_info& s2)
	{
		return s1.name()<s2.name();
	}
private:
	Handle<core> cp;
};

#endif

 

14_Student_info.cpp 

14_Handle_main_1.cpp

14_Student_info_main.cpp

二、引用计数句柄

我们通过实现引用计数句柄,避免了上面句柄类复制时副本的产生。但是仍然有问题,这种句柄当我们改变其中一个对象的值,我们就会改变其他同地址的其他对象的值。

#ifndef _GUARD_14_REF_HANDLE_CLASS_H_   
#define _GUARD_14_REF_HANDLE_CLASS_H_
/********************************************************************  
创建时间:       2012/03/31 11:18 
文件名称:       14_Ref_handle_class.h 
文件作者:       jimacs  
===================================================================== 
功能说明:       我们会为Ref_handle类添加一个指针来跟踪计数,Ref_handle所
            指向的每个对象都会有一个相关的引用计数,来跟踪记录我们有多少个这个对象的副本
			 不管需不需要这个类都避免数据的复制
*********************************************************************/  
#include <iostream>
#include <stdexcept>

template <class T>
class Ref_handle
{
public:
	Ref_handle():p(0),refptr(new std::size_t(1)) { }
	Ref_handle(T* t):p(t),refptr(new std::size_t(1)) { }
	//三位一体
	Ref_handle(const Ref_handle& h):p(h.p),refptr(h.refptr)
	{
		++*retptr;
	}
	Ref_handle& operator=(const Ref_handle&);
	~Ref_handle();
	//as before Handle class
	operator bool () const {return p;}
	T& operator* () const
	{
		if (p)
		{
			return *p
		}
		throw runtime_error("unbound Ref_handle");
	}
	T* operator->() const
	{
		if (p)
		{
			return p;
		}
		throw runtime_error("unbound Ref_handle");
	}
private:
	T* p;
	std::size_t* refptr;
};

template <class T>
Ref_handle<T> Ref_handle<T>::operator =(const Ref_handle& rhs)
{//左操作数减之前,右操作数加。如果两个操作数指向同一个对象,这种做法即保证引用计数不变,
	//同时会保证引用计数不小心成为零。
	++*rhs.refptr;
	if (0==--*refptr)
	{//如果应用计数减后成为零,说明左操作数赋值前是指向底层对象的最后一个Ref_handle。
		delete p;
		delete refptr;
	}
	p=rhs.p;
	refptr=rhs.refptr;
	return *this;
}

template <class T>
Ref_handle<T>::~Ref_handle()
{
	if (0==--*refptr)
	{
		delete p;
		delete refptr;
	}
}

#endif

三、可以决定何时共享数据的句柄类

上面两种方案,都不是很好,我们应该避免不必要的数据复制,但是也不能像上面一样,不管是否需要,这个类都会避免数据复制。

因此我们采取一种策略:只有在我们将要改变对象的内容,同时还有其他句柄指向这个对象的时候,我们的句柄类才会复制这个对象。

#ifndef _GUARD_14_PTR_FINAL   
#define _GUARD_14_PTR_FINAL 
/********************************************************************  
创建时间:       2012/03/31 16:17 
文件名称:       14_ptr(final handle).h 
文件作者:       jimacs  
===================================================================== 
功能说明:       最终的句柄类ptr,可以决定何时共享数据的句柄  
*********************************************************************/  
#include <iostream>

template <class T>
class Ptr
{
public:
	void make_unique()
	{
		if (*refptr != 1)
		{
			--*refptr;
			refptr = new std::size_t(1);
			//p=p?p->clone():0; //这里存在一个严重的问题,在Str_improvement中。
			                  //因为clone只能作为类的成员函数。万一类中没有呢?
			p=p?clone(p):0; //中间函数来解决
		}
	}
	Ptr():p(0),refptr(new std::size_t(1)) { }
	Ptr(T* t):p(t),refptr(new std::size_t(1)) { }
	Ptr(const Ptr& h):p(h.p),refptr(h.refptr)
	{
		++*retptr;
	}
	Ptr& operator=(const Ptr&);
	~Ptr();
	//as before Handle class
	operator bool () const {return p;}
	T& operator* () const
	{
		if (p)
		{
			return *p
		}
		throw runtime_error("unbound Ref_handle");
	}
	T* operator->() const
	{
		if (p)
		{
			return p;
		}
		throw runtime_error("unbound Ref_handle");
	}
private:
	T* p;
	std::size_t* refptr;
};


template <class T>
Ptr<T>& Ptr<T>::operator =(const Ptr& rhs)
{//左操作数减之前,右操作数加。如果两个操作数指向同一个对象,这种做法即保证引用计数不变,
	//同时会保证引用计数不小心成为零。
	++*rhs.refptr;
	if (0==--*refptr)
	{//如果应用计数减后成为零,说明左操作数赋值前是指向底层对象的最后一个Ref_handle。
		delete p;
		delete refptr;
	}
	p=rhs.p;
	refptr=rhs.refptr;
	return *this;
}

template <class T>
Ptr<T>::~Ptr()
{
	if (0==--*refptr)
	{
		delete p;
		delete refptr;
	}
}

template <class T> T* clone(const T* tp)
{ //通过这个函数来调用clone
	return tp->clone();
}
//模板特化(template specialization)
template <>
Vec<char>* clone(const Vec<char>* vp)
{
	return new Vec<char>(*vp);
}
#endif

 

这里上面提到了一个严重的问题,就是刚开始的clone函数,这个本来是在core类中定义的一个成员函数。从上面注释掉得一行,也可以看出,clone必须是Ptr所关联的类的成员函数,但是我们通过上面的句柄类改进以前面博文一个简单的string类Str中的Str类(因为上篇中得类会产生很多临时对象),如下:

#ifndef _GUARD_14_STR_IMPROVEMENT_H_   
#define _GUARD_14_STR_IMPROVEMENT_H_
/********************************************************************  
创建时间:       2012/03/31 19:22 
文件名称:       14_Str_improvement.h 
文件作者:       cjm  
===================================================================== 
功能说明:       原先Str类的一个改进,通过最终的句柄类Ptr   
*********************************************************************/  
#include <iostream>
#include <cstring>
#include <algorithm>
#include "14_ptr(final handle).h"
#include "Vec.h"

class Str
{
	friend std::istream& operator >> (std::istream&,Str&);
public:
	Str& operator+=(const Str& s)
	{
		data.make_unique();
		std::copy(s.data->begin(),s.data->end(),std::back_inserter(*data));
		return *this;
	}

	typedef Vec<char>::size_type size_type;

	Str():data(new Vec<char>) {}
	Str(const char* cp):data(new Vec<char>)
	{
		std::copy(cp,cp+std::strlen(cp),std::back_inserter(*data));
	}
	Str(size_type n,char c):data(new Vec<char>(n,c)) {}
	template <class In> Str(In i,In j):data(new Vec<char>)
	{
		std::opy(i,j,std::back_inserter(*data));
	}

	char& operator[](size_type i)
	{
		data.make_unique();
		return(*data)[i];
	}
	const char& operator[](size_type i) const
	{
		return (*data)[i];
	}
	size_type size()const {return data->size();}
private:
	Ptr< Vec<char> > data;
};

std::ostream& operator<<(std::ostream& ,const Str&);
Str operator+(const Str&,const Str&);

#endif

 

这里的make_unique函数中得clone,按照上面的情况应该属于Vec类,但是这是不可以的。因为这是实现的Vec类(参考c++模板函数声明定义分离编译错误详解)实际上是标准vector的一个子集,因为这个成员是标准所没有的,所以我们不应该加入Vec类中。那我们应该怎么办?

对于这个问题,我们所采用的解决方案常常是使用软件工程的基本原理:所有的问题都可以通过引用一个额外的中间转换来完成。

所以我们建立了一个全局函数:

template <class T> T* clone(const T* tp)
{ //通过这个函数来调用clone
	return tp->clone();
}

并且改变make_unique成员,使它调用新的clone函数:

	void make_unique()
	{
		if (*refptr != 1)
		{
			--*refptr;
			refptr = new std::size_t(1);
			//p=p?p->clone():0; //这里存在一个严重的问题,在Str_improvement中。
			                  //因为clone只能作为类的成员函数。万一类中没有呢?
			p=p?clone(p):0; //中间函数来解决
		}
	}

 

但是还是没有解决上面vec的问题,此时我们建立一个模板特化(template specialization)来解决。

//模板特化(template specialization)
template <>
Vec<char>* clone(const Vec<char>* vp)
{
	return new Vec<char>(*vp);
}

当把一个Vec<char>*参数传递给clone函数是,编译器会使用clone函数的特化版本,当传递其他类型的指针式,班一起就会实例化clone函数的通用版本。

ps:上面提到的core类的实现(包括派生的grad类)如下:

core_grad.h

core_grad.cpp

 

 

至此文章结束。

posted @ 2012-03-31 21:53  csqlwy  阅读(2944)  评论(1编辑  收藏  举报