C++ 对象是怎么死的?进程篇[转]

来源:互联网 发布:玩客云抢购软件下载 编辑:程序博客网 时间:2024/04/28 06:34

 

我承认这个帖子的名称有标题党的嫌疑,但是暂时想不出更好的名称了,只好先这样了 :-(
  由于前天的帖子聊了架构设计的多进程问题,所以今天想起来要聊一下和“C++进程终止”相关的那些事。与前几个C++帖子的风格类似,今天聊的内容,尽量局限于标准C++范畴,尽量不涉及特定的操作系统平台。

  ★关于进程的三种死法
  由于今天讲的是“进程篇”,自然得先搞明白进程的几种死法。其实进程和大活人一样,也有三种死法,分别是“自然死亡、自杀、它杀”。这三种死亡方式具体如下:
  1、自然死亡
  望文生义,自然死亡就是最自然的进程退出方法。具体表现为通过return语句结束main函数。由于这种方法最优雅(后面会说),如果没有其它特殊原因,强烈建议采用这种死法。
  2、自杀
   所谓的自杀,就是进程自己调用某些API来自行了断。在标准C++中,这几个函数(exit、abort、terminate、unexpected) 可以用于进程自杀。如果没有额外设置,unexpected函数默认会调用terminate函数,terminate函数默认会调用abort函数。所 以自杀的方式基本上也就是exit和abort两种。exit相对abort来说温和一些,所以下文称之为温和自杀;相对地,把abort称为激进自杀。
  3、它杀
  它杀其实也挺好理解,就是当前进程被其它进程杀死。标准C++没有提供用于它杀的API函数,因此常用的方法是通过某些跨平台的库(如ACE)提供的API函数或者调用某些外部命令(如Posix系统的kill命令)来实现。
  上面说了这几种死法,有同学要问了:进程不同的死法和C++对象有什么关系捏?其实关系大大滴,请听我细细道来。

  ★类对象的析构(销毁)
  首先把类对象分为三种:局部非静态对象、局部静态对象、全局对象(尚不清楚这几种对象差异的同学,请先找本C++入门书拜读一下)。进程不同的死法对于这几种对象是否能销毁会有很大的影响。请看如下的对照表:
------------------------------
        局部非静态对象  局部静态对象  全局对象
  自然死亡    能        能      能
  温和自杀    不能       能      能
  激进自杀    不能       不能     不能
   它杀     不能       不能     不能
------------------------------
  从这个对照表可以看出,激进自杀和它杀的效果类似(各种类对象都无法正常销毁)。所以我们在写程序时要极力避免上述这两种情况。
  另外,温和自杀也有不爽之处:不能正确地销毁局部非静态对象。准确地说,应该是:在调用exit之前已经构造但是尚未析构的局部非静态对象将再也不会被析构。所以温和自杀也要避免使用。
  综上所述,最正经、最靠谱的死法就是第一种:自然死亡。

  ★析构的顺序
  那么,是不是只要让进程自然死亡就万事大吉了?非也!即使所有的类对象都会被析构,还有另一个棘手的问题:析构的顺序。先来看下面一个例子:


class CFoo
{
public:
CFoo()
{
cout << "CFoo" << endl;
}
virtual ~CFoo()
{
cout << "~CFoo" << endl;
}
};


  上述示例挺简单的(有效代码仅6行),大伙儿能看出有什么问题吗?如果你一眼就看出问题之所在,恭喜你,后面的内容你不用看了。
  在C++标准中并没有规定全局对象和静态对象的析构顺序。由于cout本身是一个全局对象,假如CFoo类也定义了一个全局对象g_foo。当g_foo析构的时候,cout对象可能已经先死了(取决于具体的环境)。在这种情况下,CFoo析构函数的打印语句由于引用了已死的对象,可能会导致不可预料的后果。
  从上面的例子可以看出,如果你在程序中使用了全局对象或者静态对象,那你要非常小心地编写相关class/struct的析构函数代码,尽量不要在它们的析构函数中引用其它的全局对象或静态对象。
  另外,在C++经典名著《Modern C++ Design》的第6章详细描述了关于单键(Singleton)销毁的一些细节、场景及解决方法。大伙儿可以去拜读一下。
  下一个帖子,会聊一下和线程有关的C++对象是怎么死的。

原创粉丝点击