【整理】只能在堆或栈上创建对象?

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>   // 需要用到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现在就是一个禁止堆对象的类了, 如果你写下如下代码:
    NoHashObject* fp = new NoHashObject(); // 编译期错误!   
delete fp;
    上面代码会产生编译期错误. 好了, 现在你已经知道了如何设计一个禁止堆对象的类了, 你也许和我一样有这样的疑问, 难道在类NoHashObject的定义不能改变的情况下, 就一定不能产生该类型的堆对象了吗? 不, 还是有办法的, 我称之为“暴力破解法”. C++是如此地强大, 强大到你可以用它做你想做的任何事情. 这里主要用到的是技巧是指针类型的强制转换
  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;
}
    对于上面的这么多强制类型转换, 其最根本的是什么了? 可以这样理解: 某块内存中的数据是不变的, 而类型就是我们戴上的眼镜, 当我们戴上一种眼镜后, 我们就会用对应的类型来解释内存中的数据, 这样不同的解释就得到了不同的信息. 所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据.
    另外要提醒的是, 不同的编译器对对象的成员数据的布局安排可能是不一样的, 比如大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节, 这样才会保证下面这条语句的转换动作像我们预期的那样执行: Resource* rp = (Resource*)obj_ptr; 但并不一定所有的编译器都是如此. 
  
见链接:http://blog.sina.com.cn/s/blog_5d84a10b0100uqpz.html
posted on 2011-11-03 22:42  白草黒尖  阅读(3238)  评论(2编辑  收藏  举报