利用栈和堆来定义对象的不同之处~~

ps:http://www.cnblogs.com/uniqueliu/archive/2011/07/18/2109357.html

我们在定义一个类的对象的时候,通常有两种方法:

  1. 采用来定义对象:Human unique;
  2. 采用来定义对象:Human *unique=new Human;

下面,我们通过一个程序来说明这两种方法的区别:

#include <iostream>
using namespace std;
class Human
{
public:
    Human();
    ~Human();
    int get()const{return *i;}
    void set(int x){*i=x;}
private:
    int *i;
};
int main()
{
    cout<<"利用堆来定义对象:\n";
    Human *tom=new Human;
    cout<<tom->get()<<endl;
    tom->set(0);
    cout<<tom->get()<<endl;
 
    cout<<endl;
 
    cout<<"利用栈来定义对象:\n";
    Human unique;
    cout<<unique.get()<<endl;
    unique.set(0);
    cout<<unique.get()<<endl;
    return 0;  
}
Human::Human()
{
    cout<<"构造函数执行中...\n";
    i=new  int(10);
}
Human::~Human()
{
    cout<<"析构函数执行中...\n";
    delete i;
}

该程序输出结果为:

对该结果的分析:

我们在程序的第16行和第24行分别采用了堆分配(new Human)和栈分配(Human unqiue)的方式来创建了两个对象。我们从输出可以清楚的看出来,只有在利用栈分配对象的时候,编译器在最后才会自动调用该类的析构函数来销毁这个对象;而对于利用堆分配的方式,编译器是不会自动释放其所占的内存的。只有通过程序员自己在最后调用delete来释放该内存区域。即,如果我们在上面程序的第20行上面加上

delete tom;

 那么编译器才会调用析构函数来释放对象tom所占用的内存空间。结果如下图所示:

综上所述:

  1. 如果我们采用来定义对象。那么该对象会由系统自动为其在栈中间开辟空间,然后在释放对象的时候,比如说运行到右大括号的时候,系统也会自动的调用析构函数释放该对象所占用的内存空间。
  2. 如果我们采用来定义对象。即利用new的方式来分配空间给需要的对象,那么我们就必须要自行释放其所占用的内存,否则该对象所占用的内存会在整个程序全部结束的时候才会被操作系统回收。

补充:

栈对象是在适当的时候创建,然后在适当的时候自动释放的,也就是栈对象有自动管理功能。那么栈对象会在什么会自动释放了?第一,在其生命期结束的时候;第二,在其所在的函数发生异常的时候。你也许说,这些都很正常啊,没什么大不了的。是的,没什么大不了的。但是只要我们再深入一点点,也许就有意外的收获了。

  栈对象,自动释放时,会调用它自己的析构函数。如果我们在栈对象中封装资源,而且在栈对象的析构函数中执行释放资源的动作,那么就会使资源泄漏的概率大大降低,因为栈对象可以自动的释放资源,即使在所在函数发生异常的时候。实际的过程是这样的:函数抛出异常时,会发生所谓的stack_unwinding(堆栈回滚),即堆栈会展开,由于是栈对象,自然存在于栈中,所以在堆栈回滚的过程中,栈对象的析构函数会被执行,从而释放其所封装的资源。除非,除非在析构函数执行的过程中再次抛出异常――而这种可能性是很小的,所以用栈对象封装资源是比较安全的。基于此认识,我们就可以创建一个自己的句柄或代理来封装资源了。智能指针(auto_ptr)中就使用了这种技术。在有这种需要的时候,我们就希望我们的资源封装类只能在栈中创建,也就是要限制在堆中创建该资源封装类的实例。

 

禁止产生堆对象 

     那么怎样禁止产生堆对象了?我们已经知道,产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator 为private,为了对称,最好将operator delete也重载为private。现在,你也许又有疑问了,难道创建栈对象不需要调用new吗?是的,不需要,因为创建栈对象不需要搜索内存,而是直接调整堆栈指针,将对象压栈,而operator new的主要任务是搜索合适的堆内存,为堆对象分配空间,这在上面已经提到过了。好,让我们看看下面的示例代码:

 

#include <stdlib.h> //需要用到C式内存分配函数 
class Resource ; //代表需要被封装的资源类 
class NoHashObject 

{


public: 
  NoHashObject() 
  { 
   //此处可以获得需要封装的资源,并让ptr指针指向该资源 
   ptr = new Resource() ; 
  } 
  ~NoHashObject() 
  { 
   delete ptr ; //释放封装的资源 
  }   
  Resource* ptr ;//指向被封装的资源 

  ... ... //其它数据成员


private:

  void* operator new(size_t size) //非严格实现,仅作示意之用

  { 
   return malloc(size) ; 
  } 
  void operator delete(void* pp) //非严格实现,仅作示意之用 
  { 
   free(pp) ; 
  } 

}; 

难道在类NoHashObject的定义不能改变的情况下,就一定不能产生该类型的堆对象了吗?不,还是有办法的,我称之为“暴力破解法”:

 

void main(void) 

 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对象所占的堆空间。 
}

 

禁止产生栈对象 

   前面已经提到了,创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用对应的构造函数以形成一个栈对象,而当函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针收回那块栈内存。在这个过程中是不需要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 ; //防止使用悬挂指针


  现在感觉是不是好多了,生成对象和释放对象的操作一致了。

posted on 2015-05-07 20:32  会敲键盘的猩猩  阅读(149)  评论(0)    收藏  举报