zoukankan      html  css  js  c++  java
  • C++构造函数异常(一)

    C++ 构造函数的异常是一个比较难缠的问题,很多时候,我们可能不去考虑这些问题,如果被问到,有人可能会说使用RAII管理资源。

    但你真的考虑过如果构造函数失败了,到底会发生什么吗,前面构造成功的成员、基类对象应该怎样回收?

    最近在知乎上看到有人提到这个问题:

    http://www.zhihu.com/question/22889420

    看了陈硕的回答,抱着怀疑的心态写代码加以验证。

    在此之前,先不急着上代码,啰嗦几句话。

     首先问4个问题,这是从 Herb sutter 的《More Exceptrional C++》看到的,我觉得问的很好,类似保安的哲理问题:你是谁、你从哪里来、你要到哪里去?

    1:对象生命周期何时开始

    一个构造函数成功执行完毕,并成功返回之时,也就是构造函数成功执行到函数体尾,没有发生异常。

    2:对象生命周期何时结束

    当一个对像的析构函数开始执行,也就是达到析构函数开始指出,这里暂且不讨论析构函数是否发生异常,只要进入析构函数体,该对象生命周期就已经结束

    3:在生命周期开始之前,与生命结束之后,对象处于什么状态

    这时候“对象”已不是对象。理论上“它”根本就不存在

    4:接着第三个答案,如果构造函数异常,对象处于什么状态?

    构造函数异常,即构造函数甚至没有到达函数体的尾部,即对象的生命周期还没有开始,所以他根本不是一个的对象,或者说它什么都不是,

    所以更不会执行析构函数了。

    那么问题来了,如果构造失败,之前成功分配的资源怎么办呢?

    Herb sutter的答案是:这个是语言本身来负责回收了,编译器来实现,没程序员的事,即使之前成功构造的对像也不会执行析构函数。

    下面是陈硕列举构造函数失败可能发生的场景,他举了5个例子,我这里写了4个,我的结论如下

    1:构造函数的初始化列表里抛异常,前面已经构造好的成员由编译器负责回收,不会调用析构函数

    2:数组元素构造时抛异常,前面已经构造好的元素由编译器回收,不会调用对象的析构函数。

    3:多继承中某个基类的构造函数抛异常,已经构造成功的基类对象由编译器回收,不会调用析构函数

    4:智能指针,STL 容器 存放auto_ptr<T>, shared_ptr<T> 对象, 类型T构造失败,则前面构成成功的智能对象有编译器回收,不会调用析构函数。

     

    第一种:构造函数初始化列表抛出异常,前面成功构造的对像由编译器负责回收,不会调用析构函数

     1 #include<iostream>
     2 
     3 using namespace std;
     4 
     5 class B{
     6 
     7 public:
     8     B(){
     9         cout << "construct B default" << endl;
    10         throw 3; //故意在默认构造函数中抛出异常
    11     }
    12     B(int num){
    13         age = num;
    14         cout << "constructor B ,age =" << num << endl;
    15     }    
    16     ~B(){
    17         cout << "destructor B ,age=" << age << endl;
    18     }
    19 private:
    20     int age;
    21 };
    22 
    23 class A{  
    24 public:
    25     A():_data(new char[1]), b(B(10)), bp(new B()){
    26         cout << "construct A " << endl;
    27 
    28         *_data = '\0';    
    29         
    30     }
    31     ~A(){
    32         cout << "destructor A" << endl;
    33         delete [] _data;
    34         delete bp;
    35 
    36     }
    37 private:
    38     char *_data;
    39     B b;
    40     B *bp;
    41 };
    42 
    43 int main(void){
    44 
    45     A a;
    46 }

    第十行出,我故意throw 3 

    所以在25行class A构造函数的初始化列表,调用B(10)发生异常,而B()则会异常退出。结果如下

    B的默认构造函数和B(int)都执行完毕,但析构函数没有执行,当然A的构造也失败了,更不会执行析构函数,这些资源都是编译器负责回收了

    如果你不信的会,我注释第9行,是另一种结果

     第2种:数组元素构造发生异常,前面构造成功的对象由编译器负责回收,不会调用析构函数

     1  1 class C{
     2  2 public:
     3  3     C(){
     4  4         //得到指定范围[m,n]的随机数:r = rand()%(n - m + 1) + m;
     5  5         int r = rand()%(8-0)+0;
     6  6         num = r;
     7  7         cout << "constuctor C ,num = " << num << endl;
     8  8         if(!(r % 4)){
     9  9             throw r;
    10 10         }
    11 11     }
    12 12     ~C(){
    13 13         cout << "destructor C, num = " << num << endl;
    14 14     }
    15 15 private:
    16 16     int num;
    17 17 };
    18 18 
    19 19 int main(void){
    20 20 
    21 21 
    22 22     C arr[10];
    23 23     
    24 24 }

    当随机数r 整除 4是,即throw异常,则前面成功构造的对象不会析构

    若注释第9行的throw r 结果是:

    第3、4两种场景,在第二篇文章验证:《C++构造函数异常(二)》

  • 相关阅读:
    CentOS-6.8 最详细安装教程(贴镜像文件+多图)
    SSM 框架整合完整流程讲解(IDEA + Maven)
    【万字长文】Spring MVC 层层递进轻松入门 !
    Spring AOP 由浅入深学习教程【新手向】
    Spring 注解和XML两种方式配置IOC
    【万字长文】Spring框架 层层递进轻松入门 (IOC和DI)
    用idea搭建SSM项目,原来这么简单
    Java并发编程:什么是CAS?这回总算知道了
    Java并发编程:Java的四种线程池的使用,以及自定义线程工厂
    Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
  • 原文地址:https://www.cnblogs.com/geeker/p/4385445.html
Copyright © 2011-2022 走看看