私有析构函数的作用????

ywchen2000 2006-07-13 06:56:28
私有析构函数的作用????
...全文
1940 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
gattso 2006-07-14
  • 打赏
  • 举报
回复
mark
fansgq 2006-07-14
  • 打赏
  • 举报
回复
转贴:

四.禁止产生堆对象

  上面已经提到,你决定禁止产生某种类型的堆对象,这时你可以自己创建一个资源封装类,该类对象只能在栈中产生,这样就能在异常的情况下自动释放封装的资源。

  那么怎样禁止产生堆对象了?我们已经知道,产生堆对象的唯一方法是使用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++是如此地强大,强大到你可以用它做你想做的任何事情。这里主要用到的是技巧是指针类型的强制转换。

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 ; //防止使用悬挂指针

  现在感觉是不是好多了,生成对象和释放对象的操作一致了。
CHANNEL5 2006-07-14
  • 打赏
  • 举报
回复
似乎是为了导向错误,如果要使用堆(隐式或者显式),必须通过非常规的方法进行析构,而非一般化的DELETE或者自动调用析构,这样可以防止无意间使用基于堆的对象
bing_huo 2006-07-14
  • 打赏
  • 举报
回复
保证对象只能在建立在堆上 并且此类不能被继承
sinall 2006-07-14
  • 打赏
  • 举报
回复
《More Effective C++》

条款27: 要求或禁止基于堆的对象
有时你想这样管理某些对象,要让某种类型的对象能够自我销毁,也就是能够“delete this”。很明显这种管理方式需要此类型对象被分配在堆中。而其它一些时候你想获得一种保障:“不在堆中分配对象,从而保证某种类型的类不会发生内存泄漏。”如果你在嵌入式系统上工作,就有可能遇到这种情况,发生在嵌入式系统上的内存泄漏是极其严重的,其堆空间是非常珍贵的。有没有可能编写出代码来要求或禁止在基于堆的对象(heap-based object)呢?通常是可以的,不过这种代码也会把“在堆中”的概念搞得比你脑海中所想的要模糊。

要求基于堆的对象
让我们先从必须在堆中建立对象开始说起。为了执行这种限制,你必须找到一种方法禁止以调用“new”以外的其它手段建立对象。这很容易做到。非堆对象(non-heap object)在定义它的地方被自动构造,在生存时间结束时自动被释放,所以只要禁止使用隐式的构造函数和析构函数,就可以实现这种限制。

把这些调用变得不合法的一种最直接的方法是把构造函数和析构函数声明为private。这样做副作用太大。没有理由让这两个函数都是private。最好让析构函数成为private,让构造函数成为public。处理过程与条款26相似,你可以引进一个专用的伪析构函数,用来访问真正的析构函数。客户端调用伪析构函数释放他们建立的对象。

例如,如果我们想仅仅在堆中建立代表无限精度数字的对象,可以这样做:

class UPNumber {
public:
UPNumber();
UPNumber(int initValue);
UPNumber(double initValue);
UPNumber(const UPNumber& rhs);

// 伪析构函数(一个const 成员函数, 因为
// 即使是const对象也能被释放)
void destroy() const { delete this; }
...
private:
~UPNumber();
};
然后客户端这样进行程序设计:

UPNumber n; // 错误!(在这里合法,但是
// 当它的析构函数被隐式地
// 调用时,就不合法了)
UPNumber *p = new UPNumber; // 正确
...
delete p; // 错误!试图调用
// private 析构函数
p->destroy(); // 正确
paobo 2006-07-14
  • 打赏
  • 举报
回复
认真学习
睡在床板下_ 2006-07-14
  • 打赏
  • 举报
回复
学习
superxiaomm 2006-07-14
  • 打赏
  • 举报
回复
释放你所开的堆空间,尤其是指针。对象消亡,只是他内部空间消亡,但是如果他里面在用指针开空间的话,那么如果是默认方式,只会把里面的指针地址给消亡,而对堆空间不做释放。所以要自己来放。
cunsh 2006-07-14
  • 打赏
  • 举报
回复
mark
sailing0123 2006-07-14
  • 打赏
  • 举报
回复
“析构函数”是自动调用的,
为什么要那么麻烦!

“ 防止类被实例化”?
我觉得这要看类本身是怎样设计的,
控制的好就不会出问题!!
fdimim 2006-07-13
  • 打赏
  • 举报
回复
mark
lyskyly 2006-07-13
  • 打赏
  • 举报
回复
这种情况下该用protected,不然派生类也没法访问它的析构函数吧
houdy 2006-07-13
  • 打赏
  • 举报
回复
我觉得私有析构函数可以防止类被实例化。例如某些policy类,mixin类,他们是对某些概念的抽象,根本就没有实例化的必要,而且它们通常作为基类(这种情况一般允许多个基类)。这时候将析构函数设置为私有的是有好处的。
zzw820626 2006-07-13
  • 打赏
  • 举报
回复
没见过
OOPhaisky 2006-07-13
  • 打赏
  • 举报
回复
用placement new照样可以让他在栈上。
-------------------------------------
的确不错啊。

lddLinan 2006-07-13
  • 打赏
  • 举报
回复
析构函数 可以被显示调用,只有构造函数只有编译器才能显示调用
jixingzhong 2006-07-13
  • 打赏
  • 举报
回复
想了想,
似乎这个没有任何的关系吧 ...

析构函数 不能被显示调用,
只能是自动调用,
那么是否 私有 都无所谓,
因为别人不会调用它 ......
lyskyly 2006-07-13
  • 打赏
  • 举报
回复
原来如此,学习了,那Scott Meyers岂不是错了,它的
MC++上第27条用了这个技术
jixingzhong 2006-07-13
  • 打赏
  • 举报
回复
构造函数 函数私有可以防止意外类型转换等非预期情况,
但是这个 析构私有 ...

有待研究 ...
triace_zhang 2006-07-13
  • 打赏
  • 举报
回复
用placement new的确是可以创建在栈上。

class A
{
private:
~A(){};
int n;
public:
void Destory()
{delete this;}
};


int main()
{

const int num = sizeof( A );

char ia[num]; //只要在栈上开辟足够空间就行
void *d = ia;
cout << d << endl;

A *pp = new ((void*)ia) A;
cout << pp << endl;

system( "pause" );
return 1;
}

似乎只有private的构造函数有点用,析构不是很清楚。
加载更多回复(4)

65,210

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧