2013-10-21 127 views
1

我一直在玩C++中的异常处理和析构函数异常以更好地理解它,并且我遇到了一些我无法解释的奇怪行为(我希望有人在这里可以帮我)。在MSVC中析构函数抛出异常

我已经写了这个简单的代码。 这是一个异常类(Foo),在被破坏时抛出。 这里的意图是从抛出到main()的任何地方传播异常,我明确地捕获它并阻止它重新抛出它自己。

#include <iostream> 

class Foo 
{ 
public: 
    Foo(); 

    virtual ~Foo(); 

    void stopThrowing() { keepThrowing_ = false; } 

private: 
    bool keepThrowing_; 
}; 

Foo::Foo(): keepThrowing_(true) 
{ 
    std::cout << "Foo created: " << this << std::endl; 
} 

Foo::~Foo() 
{ 
    std::cout << "Foo destroyed: " << this << std::endl; 

    if (keepThrowing_) 
    { 
     throw Foo(); 
    } 
} 

int main() 
{ 
    try { 
     try { 
      throw Foo(); 
     } catch (const Foo&) { 
      std::cout << "Foo caught" << std::endl; 
     } 
    } catch (Foo& ex) { 
     std::cout << "Foo caught 2" << std::endl; 
     ex.stopThrowing(); 
    } catch (...) { 
     std::cout << "Unknown exception caught 2" << std::endl; 
    } 

    std::cout << "Done" << std::endl; 

    return 0; 
} 

我知道,这绝不应该用C++来完成,但是这并不是问题的关键 - 我想要了解什么是MSVC x86和x64异常处理之间的不同(我将在解释下一段)。

当我编译使用MSVC的x86代码(我用2010为主,但我也查了这个在2005年和2012年),一切都很好,它按预期工作:

Foo created: 001AFC1C 
Foo caught 
Foo destroyed: 001AFC1C 
Foo created: 001AF31C 
Foo caught 2 
Foo destroyed: 001AF31C 
Done 

当我编译此代码为使用MSVC 64,它失败严重:

Foo created: 000000000047F9B8 
Foo caught 
Foo destroyed: 000000000047F9B8 
Foo created: 000000000047D310 
Foo destroyed: 000000000047D310 
Foo created: 000000000047C150 
Foo destroyed: 000000000047C150 
Foo created: 000000000047AF90 
Foo destroyed: 000000000047AF90 
Foo created: 0000000000479DD0 
... 

在这一点,它使创建和销毁的Foo对象,直到它到达一个堆栈溢出,并崩溃。

如果我改变析构函数这个片段(抛出一个int,而不是美孚):

Foo::~Foo() 
{ 
    std::cout << "Foo destroyed: " << this << std::endl; 

    if (keepThrowing_) 
    { 
     throw 1; 
    } 
} 

我收到以下输出:

Foo created: 00000000008EF858 
Foo caught 
Foo destroyed: 00000000008EF858 

然后程序到达一个调试断言( std :: terminate()被调用)时,执行throw 1;

我的问题是:这里发生了什么? 它看起来像x64上的MSVC不赞成这种行为,但它只是感觉不对,因为它在x86上工作。我使用MinGW和MinGW-w64对x86和x64编译了这些代码,并且这两个程序都按预期工作。这是MSVC中的错误吗? 任何人都可以想出一种方法来绕过这个问题,或者为什么微软决定阻止这种情况发生在x64上?

谢谢。

回答

2

我相信32位和64位不同的原因是32位版本使用copy elision而64位版本不是。通过使用-fno-elide-constructors标志,我可以在gcc中重现64位版本的结果。

64位版本中发生的任何事情是任何throw Foo();行会创建一个临时对象Foo,然后将该对象复制到存储异常值的任何位置。然后销毁临时Foo,这会导致另一个throw Foo();行被执行,从而产生另一个被复制和销毁的临时数据,依此类推。如果使用print语句添加复制构造函数,则应该在64位版本中反复调用它,并且在32位版本中根本不会调用它。


至于为什么你throw 1版本要求std::terminate,这是因为如果一个异常在析构函数抛出而另一个例外仍在传播,std::terminate被调用,因为没有办法一下子应付两个例外。所以你第一个throw Foo()在主体中,那么当临时Foo被破坏时,它会在其析构函数中抛出1,但Foo异常已被处理,所以程序只是放弃并抛出std::terminate

现在您可能想知道为什么在您使用throw Foo();时不会发生这种情况。因为没有复制elision异常实际上从未实际抛出Foo::~Foo()。你的第一个throw Foo()调用主要创建一个临时Foo被复制,然后调用它的析构函数(副本尚未抛出)。在那个析构函数中,创建,复制和销毁另一个临时对象Foo,这会创建另一个临时对象Foo ...等等。所以程序不断拷贝,直到它崩溃,从未真正达到它抛出那些异常的地步。

相关问题