【C++ techniques】要求/禁止/判断—对象产生于堆中

  • 有时候我们想让某种对象具有“自杀”的能力,所以我们必须要求对象存在堆中,以便我们调用delete this;
  • 另一些时候,我们要求拥有某种确定性,保证某一些类型绝不会发生内存泄漏,原因是没有任何一个该类型的对象从堆中分配出来;堆空间非常宝贵,所以我们要对象全为栈对象,必须禁止对象产生于堆中。

要求对象产生于堆中(Heap-Based Objects)

  • 为限制对象产生于heap,我们需阻止clients不得使用new以外的方法产生对象;
  • non-heap objects会在其定义点自动构造,并在其寿命结束时自动析构,所以只需让那些被隐式调用的构造函数和析构函数不合法;
  • 比较好的方法是让destructor成为private,而constructors仍为public;
  • 如此一来,你可以导入一个pseudo destructor函数,用来调用真正的destructor。Clients则调用pseudo-destructor来销毁它们产生的对象。

例如,假设我们希望确保“表现无限精度”的数值对象只能诞生于heap之中:

class UPNumber
{
public:
	UPNumber();
	UPNumber(int initValue);
	UPNumber(double initValue);
	UPNumber(const UPNumber& rhs);
 
private:
	~UPNumber();//dtor位于private内
};
//Clients于是应该这么写:
UPNumber n; //错误!(虽然合法,但当n的dtor被隐式调用,就不合法了)

UPNumber* p = new UPNumber; //良好
...
delete p;	//错误!企图调用private destructor
p->destroy(); //良好
  • 只要限制destructor和constructors的运用,便可阻止non-heap objects的诞生,但是,它妨碍了继承和内含:
class UPNumber{...};	//将dtor或ctor声明为private
class NonNegativeUPNumber:public UPNumber{...};//错误!dtor或ctors无法通过编译
 
class Asset
{
private:
	UPNumber value;		//错误!dtor或ctors无法通过编译
	...
};

解决:

  1. 令UPNumber的destructor成为protected(并仍保持其constructors为public),便可以解决继承问题;
  2. “必须内含UPNumber对象”之classes可以修改为“内含一个指针,指向UPNumer”对象:
class UPNumber{...};	//注意将dtor声明为protected
class NonNegativeUPNumber:public UPNumber{...}; //derived classes可以调用protected members
 
class Asset
{
public:
	Asset(int initValue);
	~Asset();
	...
private:
	UPNumber* value;		
};
 
Asset::Asset(int initvalue):value(new UPNumber(initValue))
{
	...
}
 
Asset::~Asset()
{
	value->destroy();
}

判断某对象是否位于堆内

abstract mixin base class(抽象混合式基类):

  • abstract base class是一个不能被实例化的基类;
  • mixin class则提供一组定义完好的能力,能够与其派生类所可能提供的其他任何能力兼容。

我们可以形成一个所谓的抽象混合式基类,用来为派生类提供“判断某指针是否以operator new分配出来的能力:

class HeapTracked //mixin class;追踪并记录被operator new返回的指针
{
public:
	class MissingAddress{};
	
	virtual ~HeapTracked() = 0;
	
	static void* operator new(size_t size);//负责分配内存内存并将条目加入list内
	static void operator delete(void* ptr);//负责释放内存并从list身上移除条目
	
	bool isOnHeap() const; //决定某对象的地址是否在list内
	
private:
	typedef const void* RawAddress;
	static list<RawAddress> addresses; //list记录所有由operator new返回的指针
};
//HeapTracked的完整实现内容:

list<RawAddress> HeapTracked::addresses;
 
HeapTracked::~HeapTracked{}
 
void* HeapTracked::operator new(size_t size)
{
	void* memPtr = ::operator new(size);
	addresses.push_back(memPtr);
	return memPtr;
}
 
void HeapTracked::operator delete(void* ptr)
{
	list<RawAddress>::iterator it  = 
			find(addresses.begin(),addresses.end(),ptr);
			
	if(it != addresses.end())
	{
		address.erase(it);
		::operator delete(ptr);
	}else
	{
		throw MissingAddress();
	}
}
 
 
bool HeapTracked::isOnHeap() const
{
	//取得一个指针,指向*this所占内存的起始处
	const void* rawAddress = dynamic_cast<const void*>(this);
	
	list<RawAddress>::iterator it = 
		find(address.begin(),address.end(),rawAddress);
		
	return it != address.end();
}

禁止对象产生于堆中

一般而言有3种可能:

  1. 对象被直接实例化;
  2. 对象被实例化为派生类对象内的“基类”成分;
  3. 对象被内嵌于其他对象之中。

欲阻止clients直接将对象实例化于heap之中,你可以让client无法调用new:定义operator new,将其声明为private

如果不希望clients将UPNumber对象产生于堆内:

class UPNumber
{
private:
	static void* operator new(size_t size);
	static void operator delete(void* ptr);
	...
};

如果要禁止产生“UPNumer对象所组成的数组”,可以将operator new[]operator delete[]声明为private。

将operator new声明为private,往往也会妨碍UNumber对象被实例化为heap-based derived class objects的“base class成分”,因为operator new和operator delete会被继承,所以如果这些函数不在derived class声明为public,derived class继承的便是base(s)所声明的private版本:

class UPNumber{...};
class NonNegativeUPNumber:  //假设此class未声明operator new
			public UPNumber
{
	...
};
 
NonNegativeUPNumber n1;				
static NonNegativeUPNumber n2;		
NonNegativeUPNumber* p = new NonNegativeUPNumber;//错误!企图调用private operator new

如果derived class声明一个属于自己的operator new(且为public),类似于,当我们企图分配一个“内含UPNumber对象”的对象,“UNPumber的operator new乃为private“不会带来什么影响:

class Asset
{
public:
	Asset(int initValue);
	...
private:
	UPNumber value;
};
 
Asset* pa = new Asset(100); //没问题,调用的是
							//Asset::operator new或
							//::operator new而非UPNumber::operator new

我们曾经希望“如果一个UPNumber对象被构造于heap以外,那么就在UPNumber constructor内抛出异常”,这次我们希望“如果对象被产生于heap内的话,就抛出一个异常”。然而,就像没有任何根据移植性的做法可以判断某地址是否位于heap内一样,我们也没有根据移植性的做法可以判断它是否不在heap内。

你可能感兴趣的:(C++进阶,c++,开发语言,笔记)