【整理】只能在堆或栈上创建对象?
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)上生成对象
为禁止产生某种类型的堆对象, 可以自己创建一个资源封装类, 该类对象只能在栈中产生, 这样就能在异常的情况下自动释放封装的资源.
#include <stdlib.h> // 需要用到C式内存分配函数
class Resource ; // 代表需要被封装的资源类
class NoHashObject
{
private:
Resource *ptr ; // 指向被封装的资源
// ... //其它数据成员
void* operator new(size_t size) //非严格实现, 仅作示意之用
{
return malloc(size);
}
void operator delete(void* pp) //非严格实现, 仅作示意之用
{
free(pp);
}
public:
NoHashObject()
{
// 此处可以获得需要封装的资源, 并让ptr指针指向该资源
ptr = new Resource();
}
~NoHashObject()
{
delete ptr; // 释放封装的资源
}
};
NoHashObject* fp = new NoHashObject(); // 编译期错误!
delete fp;
int main()
{
char* temp = new char[sizeof(NoHashObject)];
//强制类型转换, 现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp;
temp = NULL; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换, 让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource();
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp; //释放资源
temp = (char*)obj_ptr;
obj_ptr = NULL; //防止悬挂指针产生
delete [] temp; //释放NoHashObject对象所占的堆空间.
return 0;
}