woaidongmao

文章均收录自他人博客,但不喜标题前加-[转贴],因其丑陋,见谅!~
随笔 - 1469, 文章 - 0, 评论 - 661, 引用 - 0
数据加载中……

C++对象是怎么死的

  我承认这个帖子的名称有标题党的嫌疑,但是暂时想不出更好的名称了,只好先这样了 :-(
  由于前天的帖子聊了架构设计的多进程问题,所以今天想起来要聊一下和“C++进程终止相关的那些事。与前几个C++帖子的风格类似,今天聊的内容,尽量局限于标准C++范畴,尽量不涉及特定的操作系统平台。
进程篇
  关于进程的三种死法
  由于今天讲的是进程篇,自然得先搞明白进程的几种死法。其实进程和大活人一样,也有三种死法,分别是自然死亡、自杀、它杀。这三种死亡方式具体如下:
  1、自然死亡
  望文生义,自然死亡就是最自然的进程退出方法。具体表现为通过return语句结束main函数。由于这种方法最优雅(后面会说),如果没有其它特殊原因,强烈建议采用这种死法。
  2、自杀
  所谓的自杀,就是进程自己调用某些API来自行了断。在标准C++中,这几个函数(exitabortterminateunexpected)可以用于进程自杀。如果没有额外设置,unexpected函数默认会调用terminate函数,terminate函数默认会调用abort函数。所以自杀的方式基本上也就是exitabort两种。exit相对abort来说温和一些,所以下文称exit温和自杀;相对地,把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++标准中并没有规定它们构造和析构的先后顺序;对于诸如标准输入输出流的coutcerr等全局对象,在C++ 03标准中(参见27.4.2.1.6章节)有提及如何保证它们在最后析构。但由于某些老式编译器并未完全遵照标准实现,导致标准输入输出流的几个全局对象也可能被提前析构。
  基于上述原因,假如CFoo类也定义了一个全局对象g_foo。当g_foo析构的时候,cout对象可能已经先死了(取决于具体的环境,详见关于标准输入输出流的进一步探讨)。在这种情况下,CFoo析构函数的打印语句由于引用了已死的对象,可能会导致不可预料的后果。
  从上面的例子可以看出,如果你在程序中使用了全局对象或者静态对象,那你要非常小心地编写相关class/struct的析构函数代码,尽量不要在它们的析构函数中引用其它的全局对象或静态对象。当然啦,假如能避免使用全局对象和静态对象,就更好了。
  另外,在C++经典名著《Modern C++ Design》的第6章详细描述了关于单键(Singleton)销毁的一些细节、场景及解决方法。大伙儿可以去拜读一下。
  下一个帖子,会聊一下和线程有关的C++对象是怎么死的


追求原创,欢迎转载。
转载必须包含本声明、保持本文完整。并以超链形式注明作者编程随想和本文原始地址:
http://program-think.blogspot.com/2009/02/cxx-object-destroy-with-process.html

 

Win32线程篇

  在前面的帖子里聊完了进程终止对C++对象析构的影响。今天咱们来说一下线程对于C++对象析构的影响。
  由于C++ 03标准没有包含线程的概念,而C++ 0x尚未正式发布。所以对线程的讨论只好根据特定的操作系统平台来谈。对于操作系统自带的线程API,目前比较流行的款式是Windows平台提供的线程APIPOSIX平台上的pthread API。但是这两种线程API的差异实在是太大,没法拿出来一起聊。我只好把线程篇的帖子再拆分一下,今天先来聊一聊Win32的线程API
  另外,对于进行跨平台开发的同学,应该已经用上了某些跨平台的第三方线程库(比如ACEBoost等),对于这些库的介绍,初步打算放到C++的可移植性和跨平台开发系列中。

  
两套APIOS API vs CRT API
  本来照例要先介绍线程的几种死法,但是考虑到很多Windows程序员经常混淆线程API,搞不清楚到底该用哪个。所以先来说一下两套线程API的问题。
  首先,Windows操作系统本身提供了线程的创建函数CreateThread和销毁函数ExitThread。其中的CreateThread用于创建线程,ExitThread用于在线程函数内部推出线程(也就是自杀)。
  其次,在Visual C++自带的C运行库(以下简称CRT)中,还带了另外4API函数,分别是:_beginthread_endthread_beginthreadex_endthreadex。其中的_beginthread_beginthreadex用于创建线程(它们内部调用了CreateThread),_endthread_endthreadex用于自杀(它们内部调用了ExitThread)。
  有同学看到这里,被搞懵了,心想:干嘛要搞这么多玩意儿出来糊弄人?有CreateThreadExitThread不就够了嘛!其实你有所不知,此中大有奥妙啊。
  因为OS API作为操作系统本身提供的API函数,它被设计为语言无关的。它们不光可以被C++调用,还可以被其它诸如VBPythonDelphi等开发语言来调用。所以它们不会(也不能够)帮你处理一些和具体编程语言相关的琐事。
  而CRT API虽然最终还是要调用OS API来完成核心的功能,但是CRT API在不知不觉中多帮我们干了一些虽琐碎但重要的工作。(如果同学们想窥探一下CRT API内部都干了些啥,可以拜读一下Win32编程的经典名著《Windows 核心编程》的6.7章节,里面介绍得挺细致的)
  费了这么多口水,无非是要同学们牢记:以后在Windows平台下开发多线程程序,千万不要直接使用这两个线程API(也就是CreateThreadExitThread),否则后果自负 :-)
  另外,顺便补充一下。除了上述提到的CRT库。其它一些Windows平台的C++库也可能提供了线程的启动函数(比如MFCAfxBeginThread),这些函数也对OS API进行了包装,所以用起来也是安全的。

  
三种死法
  说完了两套API,开始来讨论一下线程的几种死法。线程和进程一样,也有三种死法。详见如下:
  1、自然死亡
  一般来说,每个线程都会对应某个函数(以下称为线程函数)。线程函数是线程运行的主体。所谓的自然死亡,就是通过return语句结束线程函数的执行。
  2、自杀
  所谓的自杀,就是当前线程通过调用某API自己给停掉。前面已经说了OS API的坏话,同学们应该明白不能再用它们。那我们能否使用CRT API来进行自杀呢?请看msdn上的相关文档:http://msdn.microsoft.com/en-us/library/hw264s73.aspx。上面说了,如果使用_endthread_endthreadex,将导致析构函数不被调用。
  3、它杀
  所谓的它杀,很明显,就是其它线程通过调用某API把当前线程给强行停掉。对于Windows平台来说,实现它杀比较简单,使用TernimateThread就直接干掉了(它杀也是最野蛮的)。

  
类对象的析构
  照前一个帖子的风格,还是把类对象分为三种:局部非静态对象、局部静态对象、非局部对象。由于非局部对象是在main之前就创建、在进程死亡时析构,暂时与线程扯不上太大关系。剩下的两种局部对象,在宿主线程(所谓宿主线程,就是创建该局部对象的线程)死亡时会受到什么影响捏?请看如下的对照表:
-------------------------
       局部非静态对象  局部静态对象
 自然死亡    能        能
  自杀     不能       能
  它杀     不能       能
-------------------------
  从上述结果可以看出,Windows上线程的死法还是以自然死亡为最安全,这点和进程的死法类似。所以同学们在Windows上开发时,要尽量避免自杀和它杀。

  
关于主线程之死
  所谓主线程,就是进程启动时,操作系统为该进程默认创建的第一个线程。通俗地讲,可以把main函数看成是主线程的线程函数。
  主线程之死是有讲究的。由于前面已经阐述了非自然死亡的坏处,所以我们只讨论主线程自然死亡这一种情况。当主线程自然死亡时(也就是用returnmain返回时),会导致exit函数被调用,exit函数就会开始清除当前进程的各种资源,为进程的死亡作准备。这时候,如果还有其它活着的线程,也会被一起干掉(其效果类似于它杀)。
  为了防止出现上述情况,主线程一定要负责最终的善后工作。务必等到其它线程都死了,它才能死。

  Windows平台上,有关线程的对象析构问题,就聊到这。下一个帖子,咱们来聊一下pthread相关的对象析构话题。


转载必须包含本声明、保持本文完整。并以超链接形式注明作者编程随想和本文原始地址:
http://program-think.blogspot.com/2009/03/cxx-object-destroy-with-thread-win32.html

 

posted on 2009-03-06 23:27 肥仔 阅读(580) 评论(0)  编辑 收藏 引用 所属分类: C++ 基础


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理