c++对内存的浅谈以及内存泄漏问题的探讨之十一(2)

  头文件:
  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++是如此地强大,强大到你可以用它做你想做的任何事情。这里主要用到的是技巧是指针类型的强制转换。

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

  上面的实现是麻烦的,而且这种实现方式几乎不会在实践中使用,但是我还是写出来路,因为理解它,对于我们理解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 ; //防止使用悬挂指针

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

  ok,讲到这里,已经涉及了较多的东西,如果要把内存对象讲得更深入更全面,那可能需要写成一本书了,而就我自己的功力而言,可能是很难完全把握的。如果上面所写的能使你有所收获或启发,我就满足了。如果你要更进一步去了解内存对象方面的知识,那么我可以推荐你看看《深入探索C++对象模型》这本书。
  转载自天极网:
  http://www.yesky.com/449/1718949.shtml

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值