本文上半段借鉴了http://www.cnblogs.com/sopc-mc/archive/2011/11/03/2235124.html
1. 只能在堆(heap)上创建对象/禁止产生栈(stack)对象
创建栈对象时会移动栈顶指针以“挪出”适当大小的空间, 再在这个空间上直接调用对应的构造函数以形成一个栈对象, 而当函数返回时会调用其析构函数释放这个对象, 再调整栈顶指针收回那块栈内存, 在这个过程中是不需要operator new/delete操作的, 所以将operator new/delete设置为private不能达到禁止产生栈(stack)对象的目的.
把析构函数定义为private访问权限, 就可以保证只能在堆(heap)上创建(new)一个新的类对象.析构函数私有化的类的设计可以保证只能用new命令在堆(heap)中创建对象, 只能动态的去创建对象, 这样可以自由的控制对象的生命周期, 但这样的类需要提供创建和撤销的公共接口.
class OnlyHeapClass
{
public:
OnlyHeapClass() { }
void Destroy()
{
delete this; // 等效于"OnlyHeapClass::~OnlyHeapClass();", 写
// 成"OnlyHeapClass::~OnlyHeapClass();"更容易理
// 解public成员函数调用private析构函数.
}
private:
~OnlyHeapClass() { }
};
int main()
{
OnlyHeapClass *pInst = new OnlyHeapClass;
pInst ->Destroy(); // 如果类中没有定义Destroy()函数, 而在这里用"delete pInst;"代
// 替"pInst->Destroy();", 则会报错. 因为"delete pInst;"会去调
// 用类的析构函数, 而在类域外调用类的private成员函数必然会报错.
return 0;
}
解析:C++是一个静态绑定的语言. 在编译过程中, 所有的非虚函数调用都必须分析完成. 即使是虚函数, 也需检查可访问性. 在栈(stack)上生成对象时, 对象会自动析构, 即析构函数必须可以访问. 而在堆(heap)上生成对象, 由于析构时机由程序员控制,所以不一定需要析构函数.
将析构函数设为private除了会限制栈对象生成外, 还会限制继承. 如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private. 为了限制栈对象,却不限制继承, 可将析构函数声明为protected, 这样就两全其美了.如下代码所示:
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
接着,可以像这样使用NoStackObject类:
NoStackObject* hash_ptr = new NoStackObject();
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy();
是不是觉得有点怪怪的: 用new创建一个对象, 却不是用delete去删除它, 而是要用destroy方法,用户是不习惯这种怪异的使用方式的, 所以将构造函数也设为private或protected, 但需要让该类提供一个static成员函数专门用于产生该类型的堆对象. (设计模式中的singleton模式就可以用这种方式实现.)让我们来看看:
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
NoStackObject* hash_ptr = NoStackObject::creatInstance();
... ... //对hash_ptr指向的对象进行操作
hash_ptr->destroy();
hash_ptr = NULL; //防止使用悬挂指针
2. 只能在栈(stack)上生成对象
为禁止产生某种类型的堆对象, 可以自己创建一个资源封装类, 该类对象只能在栈中产生,这样就能在异常的情况下自动释放封装的资源.
产生堆对象的唯一方法是使用new, 禁止使用new就可禁止产生堆对象. 由于new执行时会调用operator new, 而operator new是可重载的, 所以将operator new和operator delete重载为private即可. 创建栈对象不需要调用new, 因为创建栈对象不需要搜索内存, 而是直接调整堆栈指针将对象压栈, 而operator new的主要任务是搜索合适的堆内存, 为堆对象分配空间.
#include <stdlib.h>
#include <iostream>
#include<new>
using namespace std;
class NoHashObject
{
private:
int* ptr;
void* operator new(size_t s)
{
return malloc(s);
}
void operator delete(void* pp) //非严格实现, 仅作示意之用
{
free(pp);
}
public:
NoHashObject()
{
cout << "ok";
// 此处可以获得需要封装的资源, 并让ptr指针指向该资源
ptr = new int;
}
~NoHashObject()
{
delete ptr;
}
};
NoHashObject现在就是一个禁止堆对象的类了,如果你写下如下代码:
NoHashObject* fp = new NoHashObject(); // 编译期错误!
delete fp;
上面代码会产生编译期错误. 好了, 现在你已经知道了如何设计一个禁止堆对象的类了, 你也许和我一样有这样的疑问,难道在类NoHashObject的定义不能改变的情况下, 就一定不能产生该类型的堆对象了吗?我们先来分析以下为什么上面这个类不能在堆上建立。
首先当我们new一个对象时,会先调用operator new函数去分配内存(这个函数简单理解的话其实就是间接调用了malloc函数,),在内存申请成功后再在那块内存上调用构造函数,然后再把这段内存返回。而当我们重载了自己的operator new函数之后,默认是先调用自己的,然而我们把它定义成了private自然就不能访问,所以无法new出来一个对象。那么我们顺着这个思路看看能不能绕开间接在堆上建立一个对象。关于这一段我重翻了《C++ primer》并得到了验证:
我首先想到的是既然是因为调用的operator new是私有的才导致无法new一个对象,那我直接去调用那个全局的operator new函数不就可以了么?
int main()
{
NoHashObject* o1 = (NoHashObject*)(::operator new(sizeof(NoHashObject)));
}
编译器没报错也正常运行了,但仔细看我却发现尽管内存分配成功了,但是构造函数却没调用,那么这就不算是成功在堆上建立了对象,然后我用了一个最近新学的技巧 placement new 加上了两行代码来看看发生了什么?
int main()
{
NoHashObject* o1 = (NoHashObject*)(::operator new(sizeof(NoHashObject)));
void* v = (void*)o1;
NoHashObject* o2 = ::new(v) NoHashObject;
}
运行之后我发现构造函数成功调用了,那么内存分配成功并且也在这片内存上调用了构造函数那不是说明这个类并不能完全保证只能在栈上分配,这个问题我发到群里暂时没有人回应我,但是我觉得这应该算是奇技淫巧,最好不要去模仿了。
关于placement new简单介绍下,在《effective C++》中讲到了如果operator new除了一定会有的那个size_t参数之外还有其他的这便是一个所谓的placement new。而众多placement new版本中特别有用的一个是“接受一个指针指向对象被构造之处”,那样的operator new长相如下:
void* operator new(std::size_t,void* pMemory )throw();
这个版本的new已经纳入了C++标准程序库,你只要#include<new>就可以取用它,它可以在已经存在的堆内存上建立对象。