第7集 构造函数中抛出的异常

上一篇文章简单讨论了一下对象的成员函数抛出异常时的处理情况。本文中将继续讨论当在构造函数中抛出异常时,程序的执行情况又如何?这有点复杂呀!而且主人公阿愚还觉得这蛮有点意思!http://se.csai.cn/ExpertEyes/200801031103481314.htm

  构造函数中抛出的异常

  1、标准C++中定义构造函数是一个对象构建自己,分配所需资源的地方,一旦构造函数执行完毕,则表明这个对象已经诞生了,有自己的行为和内部的运行状态,之后还有对象的消亡过程(析构函数的执行)。可谁能保证对象的构造过程一定能成功呢?说不定系统当前的某个资源不够,导致对象不能完全构建好自己(人都有畸形儿,更何况别的呢?朋友们!是吧!),因此通过什么方法来表明对象的构造失败了呢?C++程序员朋友们知道,C++中的构造函数是没有返回值的,所以不少关于C++编程方面的书上得出结论:“因为构造函数没有返回值,所以通知对象的构造失败的唯一方法那就是在构造函数中抛出异常”。主人公阿愚非常不同意这种说法,谁说的,便不信邪!虽然C++标准规定构造函数是没有返回值,可我们知道每个函数实际上都会有一个返回值的,这个值被保存在eax寄存器中,因此实际上是有办法通过编程来实现构造函数返回一个值给上层的对象创建者。当然即便是构造函数真的不能有返回值,我们也可以通过一个指针类型或引用类型的出参来获知对象的构造过程的状态。示例如下:

  class MyTest_Base

  {

  public:

  MyTest_Base (int& status)

  {

  //do other job

  // 由于资源不够,对象构建失败

  // 把status置0,通知对象的构建者

  status = 0;

  }

  protected:

  };

  void main()

  {

  int status;

  MyTest_Base obj1(status);

  // 检查对象的构建是否成功

  if(status ==0) cout << “对象构建失败” << endl;

  }

  程序运行的结果是:

  对象构建失败

  是啊!上面我们不也得到了对象构造的成功与否的信息了吗?可大家有没有觉得这当中有点问题?主人公阿愚建议大家在此停留片刻,仔细想想它会有什么问题?OK!也许大家都知道了问题的所在,来验证一下吧!

  class MyTest_Base

  {

  public:

  MyTest_Base (int& status)

  {

  //do other job

  // 由于资源不够,对象构建失败

  // 把status置0,通知对象的构建者

  status = 0;

  }

  virtual ~ MyTest_Base ()

  {

  cout << “销毁一个MyTest_Base类型的对象” << endl;

  }

  protected:

  };

  void main()

  {

  int status;

  MyTest_Base obj1(status);

  // 检查对象的构建是否成功

  if(status ==0) cout << “对象构建失败” << endl;

  }

  程序运行的结果是:

  对象构建失败

  销毁一个MyTest_Base类型的对象

  没错,对象的析构函数被运行了,这与C++标准中所规定的面向对象的一些特性是有冲突的。一个对象都没有完成自己的构造,又何来析构!好比一个夭折的畸形儿还没有出生,又何来死之言。因此这种方法是行不通的。那怎么办?那就是上面那个结论中的后一句话是对的,通知对象的构造失败的唯一方法那就是在构造函数中抛出异常,但原因却不是由于构造函数没有返回值而造成的。恰恰相反,C++标准中规定构造函数没有返回值正是由于担心很容易与面向对象的一些特性相冲突,因此干脆来个规定,构造函数不能有返回值(主人公阿愚的个人理解,有不同意见的朋友欢迎讨论)。

  2、构造函数中抛出异常将导致对象的析构函数不被执行。哈哈^-^,阿愚很开心,瞧瞧!如果没有C++的异常处理机制鼎立支持,C++中的面向对象特性都无法真正实现起来,C++标准总不能规定所有的对象都必须成功构造吧!这也太理想化了,也许只有等到共产主义社会实现的那一天(CPU可以随便拿,内存可以随便拿,所有的资源都是你的!)才说不定有可能·····,所以说C++的异常处理和面向对象确实是谁也离不开谁。当然示例还是要看一下,如下:

  class MyTest_Base

  {

  public:

  MyTest_Base (string name = “”) : m_name(name)

  {

  throw std::exception(“在构造函数中抛出一个异常,测试!”);

  cout << “构造一个MyTest_Base类型的对象,对象名为:”<<m_name p="" <<="" endl;<="">

  }

  virtual ~ MyTest_Base ()

  {

  cout << “销毁一个MyTest_Base类型的对象,对象名为:”<<m_name p="" <<="" endl;<="">

  }

  void Func() throw()

  {

  throw std::exception(“故意抛出一个异常,测试!”);

  }

  void Other() {}

  protected:

  string m_name;

  };

  void main()

  {

  try

  {

  // 对象构造时将会抛出异常

  MyTest_Base obj1(“obj1”);

  obj1.Func();

  obj1.Other();

  }

  catch(std::exception e)

  {

  cout << e.what() << endl;

  }

  catch(...)

  {

  cout << “unknow exception”<< endl;

  }

  }

  程序的运行结果将会验证:“构造函数中抛出异常将导致对象的析构函数不被执行”

  3、是不是到此,关于构造函数中抛出异常的处理的有关讨论就能结束了呢?非也!非也!主人公阿愚还有进一步的故事需要讲述!来看一个更复杂一点的例子吧!如下:

  class MyTest_Base

  {

  public:

  MyTest_Base (string name = "") : m_name(name)

  {

  cout << "构造一个MyTest_Base类型的对象,对象名为:"<<m_name p="" <<="" endl;<="">

  }

  virtual ~ MyTest_Base ()

  {

  cout << "销毁一个MyTest_Base类型的对象,对象名为:"<<m_name p="" <<="" endl;<="">

  }

  void Func() throw()

  {

  throw std::exception("故意抛出一个异常,测试!");

  }

  void Other() {}

  protected:

  string m_name;

  };

  class MyTest_Parts

  {

  public:

  MyTest_Parts ()

  {

  cout << "构造一个MyTest_Parts类型的对象" << endl;

  }

  virtual ~ MyTest_Parts ()

  {

  cout << "销毁一个MyTest_Parts类型的对象"<< endl;

  }

  };

  class MyTest_Derive : public MyTest_Base

  {

  public:

  MyTest_Derive (string name = "") : m_component(), MyTest_Base(name)

  {

  throw std::exception("在MyTest_Derive对象的构造函数中抛出了一个异常!");

  cout << "构造一个MyTest_Derive类型的对象,对象名为:"<<m_name p="" <<="" endl;<="">

  }

  virtual ~ MyTest_Derive ()

  {

  cout << "销毁一个MyTest_Derive类型的对象,对象名为:"<<m_name p="" <<="" endl;<="">

  }

  protected:

  MyTest_Parts m_component;

  };

  void main()

  {

  try

  {

  // 对象构造时将会抛出异常

  MyTest_Derive obj1("obj1");

  obj1.Func();

  obj1.Other();

  }

  catch(std::exception e)

  {

  cout << e.what() << endl;

  }

  catch(...)

  {

  cout << "unknow exception"<< endl;

  }

  }

  程序运行的结果是:

  构造一个MyTest_Base类型的对象,对象名为:obj1

  构造一个MyTest_Parts类型的对象

  销毁一个MyTest_Parts类型的对象

  销毁一个MyTest_Base类型的对象,对象名为:obj1

  在MyTest_Derive对象的构造函数中抛出了一个异常!

  上面这个例子中,MyTest_Derive从MyTest_Base继承,同时MyTest_Derive还有一个MyTest_Parts类型的成员变量。现在MyTest_Derive构造的时候,是在父类MyTest_Base已构造完毕和MyTest_Parts类型的成员变量m_component也已构造完毕之后,再抛出了一个异常,这种情况称为对象的部分构造。是的,这种情况很常见,对象总是由不断的继承或不断的聚合而来,对象的构造过程实际上是这些所有的子对象按规定顺序的构造过程,其中这些过程中的任何一个子对象在构造时发生异常,对象都不能说自己完成了全部的构造过程,因此这里就有一个棘手的问题,当发生对象的部分构造时,对象将析构吗?如果时,又将如何析构呢?

  从运行结果可以得出如下结论:

  (1) 对象的部分构造是很常见的,异常的发生点也完全是随机的,程序员要谨慎处理这种情况;

  (2) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构(即异常发生点前面的对象);而还没有开始构建的子对象将不会被构造了(即异常发生点后面的对象),当然它也就没有析构过程了;还有正在构建的子对象和对象自己本身将停止继续构建(即出现异常的对象),并且它的析构是不会被执行的。

  构造函数中抛出异常时概括性总结

  (1) C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;

  (2) 构造函数中抛出异常将导致对象的析构函数不被执行;

  (3) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构;

  (4) 哈哈^-^,其是还是那句话, “C++的异常处理不会破坏任何一条面向对象的特性!”,因此主人公阿愚再次建议朋友们,牢牢记住这一条!

  下一篇文章讨论在对象的析构函数中抛出异常时程序的执行情况,这不仅有些复杂,而且很关键,它对我们的软件系统影响简直太大了,可许多人并未意识到这个问题的严重性!朋友们,不要错过下一篇文章,继续吧!

原文地址:https://www.cnblogs.com/robbychan/p/3786696.html