`
zhangziyueup
  • 浏览: 1178031 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

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

构造函数中抛出的异常

  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 << endl;
}

virtual ~ MyTest_Base ()
{
cout << “销毁一个MyTest_Base类型的对象,对象名为:”<<m_name << 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 << endl;
}

virtual ~ MyTest_Base ()
{
cout << "销毁一个MyTest_Base类型的对象,对象名为:"<<m_name << 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 << endl;
}

virtual ~ MyTest_Derive ()
{
cout << "销毁一个MyTest_Derive类型的对象,对象名为:"<<m_name << 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++的异常处理不会破坏任何一条面向对象的特性!”,因此主人公阿愚再次建议朋友们,牢牢记住这一条!

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

分享到:
评论

相关推荐

    JSTL详细标签库介绍

    java异常处理的基础:&lt;BR&gt;&lt;BR&gt;java的异常处理适用于在一个方法中能够检测出错误单不能处理错误的情况,这样方法将抛出一个异常(JAVA无法保证“存在”的异常处理程序能够处理某种异常,若“存在”,就“捕获”异常,...

    C++编程思想习题

    17.2抛出异常 17.3异常捕获 17.3.1try块 17.3.2异常处理器 17.3.3异常规格说明 17.3.4更好的异常规格说明 17.3.5捕获所有异常 17.3.6异常的重新抛出 17.3.7未被捕获的异常 17.4清除 17.5构造函数 17.6异常匹配 17.7...

    Absolute C++中文版(原书第2版)-完美的C++教程,文档中还包含英文版

    第7章 构造函数及其他工具 179 7.1 构造函数 179 7.1.1 构造函数的定义 179 7.1.2 构造函数的显式调用 183 7.1.3 类类型成员变量 190 7.2 其他工具 193 7.2.1 const参数修饰词 193 7.2.2 内联函数 198 7.2.3...

    C++ Primer第四版【中文高清扫描版】.pdf

    第7章 函数 195 7.1 函数的定义 196 7.1.1 函数返回类型 197 7.1.2 函数形参表 198 7.2 参数传递 199 7.2.1 非引用形参 199 7.2.2 引用形参 201 7.2.3 vector和其他容器类型的形参 206 7.2.4 数组形参 206 7.2.5 ...

    C++ Primer中文版(第5版)李普曼 等著 pdf 1/3

    C++ Primer中文版(第5版... 18.1.1 抛出异常 684  18.1.2 捕获异常 687  18.1.3 函数try语句块与构造函数 689  18.1.4 noexcept异常说明 690  18.1.5 异常类层次 693  18.2 命名空间 695  18.2.1 命名空间定义...

    《Java和Android开发实战详解》第6到10章源代码-by 南邮-陈杨

    10.2 抛出异常与自定义Exception类 196 10.2.1 使用throw关键字 196 10.2.2 在方法抛出异常 197 10.2.3 自定义Exception类 199 10.3 线程的基础知识 200 10.4 创建Java的线程 201 10.4.1 实现Runnable...

    C++Primer(第5版 )中文版(美)李普曼等著.part2.rar

    C++ Primer中文版(第5版... 18.1.1 抛出异常 684  18.1.2 捕获异常 687  18.1.3 函数try语句块与构造函数 689  18.1.4 noexcept异常说明 690  18.1.5 异常类层次 693  18.2 命名空间 695  18.2.1 命名空间定义...

    visualC++2010入门经典源代码

    6.3.1 抛出异常 253 6.3.2 捕获异常 254 6.3.3 mfc中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 257 6.5.1 函数重载的概念 258 6.5.2 引用类型和重载选择 260 6.5.3 何时重载函数 260 6.6 函数...

    [Visual.C++.2010入门经典(第5版)].Ivor.Horton.part1

    6.3.1 抛出异常 253 6.3.2 捕获异常 254 6.3.3 mfc中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 257 6.5.1 函数重载的概念 258 6.5.2 引用类型和重载选择 260 6.5.3 何时重载函数 260 6.6 函数模板 261 ...

    Visual C++ 2005入门经典.part08.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

    Visual C++ 2005入门经典.part04.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

    Visual C++ 2005入门经典.part07.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

    Visual C++ 2005入门经典.part09.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

    Visual C++ 2005入门经典.part06.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

    Visual C++ 2005入门经典.part05.rar (整理并添加所有书签)

    6.3.1 抛出异常 6.3.2 捕获异常 6.3.3 MFC中的异常处理 6.4 处理内存分配错误 6.5 函数重载 6.5.1 函数重载的概念 6.5.2 何时重载函数 6.6 函数模板 6.7 使用函数的示例 6.7.1 实现计算器 6.7.2 从字符串中删除空格 ...

Global site tag (gtag.js) - Google Analytics