2015-02-05 79 views
12

当我们超出catch块范围,异常析构函数被调用? (如果我们不重新抛出它)异常与非虚拟析构函数C++

假设我有类A,并且它的析构函数不是虚拟的。 乙继承A. 假设一些函数抛出B级的对象,作为异常, ,它是由一个catch块

catch(A& a){ 
... 
} 

抓到如果异常析构函数应该被称为当外出捕范围, 在这种情况下只有基类A的析构函数会被调用?

Cornstalks: 现场试用结果调用这两个类的析构函数。

它违背了我的逻辑。解释某人?

+1

我对你为什么问这个问题有点感兴趣;这是一个非常有效的,并非真正基本的问题,但它表明你关心何时调用异常析构函数的时间点,这是你通常不会做的。 – 2015-02-05 20:52:13

+0

嗯,我不确定最后一部分。 – 2015-02-05 20:53:11

+0

@MarcusMüller:想要获得关于我们使用的工具的知识有什么问题? – 2015-02-05 20:53:27

回答

5

好的,有人已经回答了你的第一个问题。我将专注于此:

如果应该在超出捕获范围时调用异常析构函数,那么在这种情况下只会调用基类A的判定函数?

实现将始终销毁异常对象,无论它如何被捕获。该实现构造了异常对象,所以它知道如何销毁它。这与您通过指针调用delete时不一样,因为在那种情况下,除非存在虚拟析构函数,否则在那一点上关于该对象的完整类型的信息不完整(可能是其他地方的new)。

如果不是这种情况,catch (...)根本就不起作用。

+0

(它可能已经在其他地方被刷新了) - 你能解释一下吗? – 2015-02-05 21:22:33

+3

而且,为了完整性,通过指向基类型的指针删除指向派生对象的指针,当基类型没有虚析构函数时,会产生**未定义的行为**。它可能运行基本的析构函数,但它可能会做一些完全不同的事情。 – 2015-02-05 21:49:39

+0

@Day_Dreamer对不起,这是一句尴尬的句子,但重点是,在代码中指针为'delete'd的地方,没有办法将它与相同对象的位置相匹配, new'ed。这就是为什么你需要虚拟析构函数,否则代码无法“知道”要调用哪个析构函数。 – Brian 2015-02-05 21:53:47

5

当我们走出catch catch作用域时,异常析构函数被调用了吗? (如果我们不重新抛出)

是:

[C++11: 15.1/4]:[..]异常对象为以任何方式异常退出或者最后剩下的活动处理程序后销毁除了重新抛出之外,或类型std::exception_ptr(18.8.5)中指向异常对象的最后一个对象被销毁,以较晚者为准。 [..]


如果异常析构函数应该被称为当外出捕范围,在这种情况下,只有基类的D'TOR会叫什么名字?

No

#include <iostream> 

struct A 
{ 
    A() { std::cout << "A()"; } 
    A(const A&) { std::cout << "A(const A&)"; } 
    A(A&&) { std::cout << "A(A&&)"; } 
    ~A() { std::cout << "~A()"; } 
}; 

struct B : A 
{ 
    B() { std::cout << "B()"; } 
    B(const B&) { std::cout << "B(const B&)"; } 
    B(B&&) { std::cout << "B(B&&)"; } 
    ~B() { std::cout << "~B()"; } 
}; 

int main() 
{ 
    try { 
     throw B(); 
    } 
    catch (A&) { 
    } 
} 

// Output: A()B()~B()~A() 
+0

其实,你的样品表明否则。我得到下面的输出(使用相同的在线编译器):〜B()〜A() – zdan 2015-02-05 21:08:17

+1

Ahem,Lightness,你的coliru链接同时显示'〜B()'和'〜A()'...这是Cornstalks的回答证实了什么......只是说' – 2015-02-05 21:11:57

+0

@InnocentBystander:我想不可能判断'〜B()'是否是_throw-expression_中的暂时,或者是否被忽略(这是合法的),而我们'看到'catch'的输出。 – 2015-02-05 21:50:10

3

虽然我不是从标准报价,似乎扔B和受凉A&会导致两个A的和B的析构函数获取调用。 Live demo

#include <iostream> 

struct A 
{ 
    ~A() { std::cout << "A::~A" << std::endl; } 
}; 

struct B : public A 
{ 
    ~B() { std::cout << "B::~B" << std::endl; } 
}; 

void throwit() 
{ 
    throw B{}; 
} 

int main() 
{ 
    std::cout << "beginning main scope" << std::endl; 

    { 
     std::cout << "beginning inner scope" << std::endl; 

     try 
     { 
      std::cout << "calling throwit()" << std::endl; 
      throwit(); 
     } 
     catch (A& a) 
     { 
      std::cout << "caught exception" << std::endl; 
     } 

     std::cout << "ending inner scope" << std::endl; 
    } 

    std::cout << "ending main scope" << std::endl; 
} 

输出:

开始主要范围
开始内部范围
调用throwit()
捕获的异常
B ::〜乙
A ::〜甲
结束内部范围
结束主要范围

正如你所看到的,这两个析构函数都会被调用。额外的范围打印非常清楚地显示了何时调用析构函数(在catch块的末尾)。

3

每当标准说某个对象被销毁时,这意味着调用了正确的最大派生析构函数。

总是。

当你多态删除对象不虚析构函数,或者您终止(通过delete运营商或明确的析构函数调用)不完全类型的对象,并在适当的析构函数是不平凡的,标准不说,对象被销毁。它没有说调用基类析构函数。它说你有未定义的行为。