如何保证只能在堆(heap)上创建对象?

转载 2009年11月06日 09:43:00
代码:
总结:  把析构函数定义为private访问权限, 就可以保证只能在堆(heap)上创建(new)一个新的类对象.
   
    原因是C++是一个静态绑定的语言. 在编译过程中, 所有的非虚函数调用都必须分析完成. 即使是虚函数, 也需检查可访问性. 因些, 当在栈(stack)上生成对象时, 对象会自动析构, 也就说析构函数必须可以访问. 而堆上生成对象, 由于析构时机由程序员控制, 所以不一定需要析构函数. 保证了不能在栈上生成对象后, 需要证明能在堆上生成它. 这里OnlyHeapClass与一般对象唯一的区别在于它的析构函数为私有, delete操作会调用析构函数, 所以不能编译.

    那么如何释放它呢? 答案也很简单, 提供一个成员函数, 完成delete操作. 在成员函数中, 析构函数是可以访问的, 当然detele操作也是可以编译通过.

    析构函数私有化的类的设计可以保证只能用new命令在堆(heap)中创建对象, 只能动态的去创建对象, 这样可以自由的控制对象的生命周期. 但是, 这样的类需要提供创建和撤销的公共接口.

    另外重载delete, new为私有可以达到要求对象创建于栈上的目的, 用placement new也可以创建在栈上.

 
四.禁止产生堆对象
    上面已经提到, 你决定禁止产生某种类型的堆对象, 这时你可以自己创建一个资源封装类, 该类对象只能在栈中产生, 这样就能在异常的情况下自动释放封装的资源.
    那么怎样禁止产生堆对象了? 我们已经知道, 产生堆对象的唯一方法是使用new操作, 如果我们禁止使用new不就行了么. 再进一步, new操作执行时会调用operator new, 而operator new是可以重载的. 方法有了, 就是使new operator为private, 为了对称, 最好将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;   
  }    
    上面的实现是麻烦的, 而且这种实现方式几乎不会在实践中使用, 但是我还是写出来路, 因为理解它, 对于我们理解C++内存对象是有好处的. 对于上面的这么多强制类型转换, 其最根本的是什么了? 我们可以这样理解:   
   
    某块内存中的数据是不变的, 而类型就是我们戴上的眼镜, 当我们戴上一种眼镜后, 我们就会用对应的类型来解释内存中的数据, 这样不同的解释就得到了不同的信息.   
   
    所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据.   
       
    另外要提醒的是, 不同的编译器对对象的成员数据的布局安排可能是不一样的, 比如, 大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节, 这样才会保证下面这条语句的转换动作像我们预期的那样执行:   
   
  Resource*   rp   =   (Resource*)obj_ptr   ;    
   
    但是, 并不一定所有的编译器都是如此.   
   
    既然我们可以禁止产生某种类型的堆对象, 那么可以设计一个类, 使之不能产生栈对象吗? 当然可以.   
   
    五.禁止产生栈对象  
   
    前面已经提到了, 创建栈对象时会移动栈顶指针以“挪出”适当大小的空间, 然后在这个空间上直接调用对应的构造函数以形成一个栈对象, 而当函数返回时, 会调用其析构函数释放这个对象, 然后再调整栈顶指针收回那块栈内存. 在这个过程中是不需要operator   new/delete操作的, 所以将operator   new/delete设置为private不能达到目的. 当然从上面的叙述中, 你也许已经想到了: 将构造函数或析构函数设为私有的, 这样系统就不能调用构造/析构函数了, 当然就不能在栈中生成对象了.   
   
    这样的确可以, 而且我也打算采用这种方案. 但是在此之前, 有一点需要考虑清楚,那就是, 如果我们将构造函数设置为私有, 那么我们也就不能用new来直接产生堆对象了, 因为new在为对象分配空间后也会调用它的构造函数啊. 所以, 我打算只将析构函数设置为private. 再进一步, 将析构函数设为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. 这又回到了上面曾试图避免的问题, 即不用new, 那么该用什么方式来生成一个对象了? 我们可以用间接的办法完成, 即让这个类提供一个static成员函数专门用于产生该类型的堆对象. (设计模式中的singleton模式就可以用这种方式实现. )让我们来看看:   
   
  class   NoStackObject  
  {  
   protected:  
    NoStackObject()   {   }  
    ~NoStackObject()   {   }  
   public:  
    static   NoStackObject*   creatInstance()  
    {  
     return   new   NoStackObject()   ;//调用保护的构造函数  
    }  
    void   destroy()  
    {  
     delete   this   ;//调用保护的析构函数  
    }  
  };    
   
    现在可以这样使用NoStackObject类了:   
   
  NoStackObject*   hash_ptr   =   NoStackObject::creatInstance()   ;  
  ...   ...   //对hash_ptr指向的对象进行操作  
  hash_ptr->destroy()   ;  
  hash_ptr   =   NULL   ;   //防止使用悬挂指针    
   
    现在感觉是不是好多了, 生成对象和释放对象的操作一致了.   

如何保证只能在堆(heap)上创建对象?

代码: [cpp] view plain copy  print? class OnlyHeapClass   {   public:       OnlyHeapClass() ...
  • yanxinrui0027
  • yanxinrui0027
  • 2017年04月10日 19:45
  • 138

C++ 如何让类对象只在堆或栈上创建

今天温习C++时,发现一个很有意思的题目: 在C++中,为了让某个类只能通过new来创建(即如果直接创建对象,编译器将报错),应该() 正确答案: B   你的答案: D (错误) 将构造函数...
  • qq_30835655
  • qq_30835655
  • 2017年04月01日 00:06
  • 791

在堆中创建对象

我们既然可以在堆中保存变量,那么也就可以保存对象,我们可以将对象保存在堆中,然后通过指针来访问它。   #include  using namespace std; int main() {...
  • u010648606
  • u010648606
  • 2014年05月20日 21:25
  • 537

堆(heap)和栈(stack)有什么区别??

堆栈存放用户自己用malloc分配的空间,用free来释放;由用户自己管理,而栈存放函数的局部变量,由编译器来负责分配和回收管理,速度快,但容量有限, 简单的可以理解为:  heap:是由ma...
  • ly0303521
  • ly0303521
  • 2015年04月09日 19:40
  • 1661

只能在堆或栈上创建对象?

【整理】只能在堆或栈上创建对象? 1. 只能在堆(heap)上创建对象/禁止产生栈(stack)对象        创建栈对象时会移动栈顶指针以“挪出”适当大小的空间, 再在这个空间上直接...
  • helinsen
  • helinsen
  • 2013年04月28日 14:09
  • 294

数据结构-堆(heap)

堆(heap)也被称为优先队列(priority queue)。队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头取出元素。而堆也是一样,在堆底插入元素,在堆顶取出元素,但是堆中元素的排列...
  • juanqinyang
  • juanqinyang
  • 2016年05月15日 20:00
  • 1528

数据结构之堆(Heap)及其用途

图、码、文 介绍 优先队列之堆 优先队列;最大树;最大堆、最小堆的插入、删除、初始化; 堆的用途:堆排序;haffman编码;haffman tree 解决优化问题...
  • a1459268562
  • a1459268562
  • 2016年12月07日 18:57
  • 252

c++中堆和栈的的区别(stack vs heap)

 堆和栈的区别 一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操...
  • hyqsong
  • hyqsong
  • 2014年12月18日 15:57
  • 2698

数据结构之堆(Heap)的实现

堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构,所以堆也叫做二叉堆。二叉堆满足二个特性:  1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。  2.每个结点的左子树和右子树...
  • leex_brave
  • leex_brave
  • 2016年05月24日 15:28
  • 2878

java内存组成介绍:堆(Heap)和非堆(Non-heap)内存

来源: http://www.cnblogs.com/redcreen/archive/2011/05/04/2036387.html java内存组成介绍:堆(Heap)和...
  • suntimv
  • suntimv
  • 2016年11月26日 19:51
  • 587
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:如何保证只能在堆(heap)上创建对象?
举报原因:
原因补充:

(最多只允许输入30个字)