2013-11-28 64 views
5

为什么这有效?C++异常继承模糊

#include <exception> 
#include <iostream> 
#include <stdexcept> 

#include <boost/exception/all.hpp> 

struct foo_error : virtual boost::exception, public std::runtime_error 
{ 
    explicit foo_error(const char* what) 
     : std::runtime_error(what) 
    { } 

    explicit foo_error(const std::string& what) 
     : std::runtime_error(what) 
    { } 
}; 

struct bar_error : virtual boost::exception, public std::runtime_error 
{ 
    explicit bar_error(const char* what) 
     : std::runtime_error(what) 
    { } 

    explicit bar_error(const std::string& what) 
     : std::runtime_error(what) 
    { } 
}; 

struct abc_error : virtual foo_error, virtual bar_error 
{ 
    explicit abc_error(const char* what) 
     : foo_error(what), bar_error(what) 
    { } 

    explicit abc_error(const std::string& what) 
     : foo_error(what), bar_error(what) 
    { } 
}; 

static void abc() 
{ 
    throw abc_error("abc error"); 
} 

int main() 
{ 
    try 
    { 
     abc(); 
    } 
    catch (const std::exception& e) 
    { 
     std::cerr << e.what(); 
    } 
} 

我想这应该不是由于暧昧转换编译从abc_errorstd::exception。我错过了什么?我想出了继承关系图,但我无法弄清楚为什么这段代码能够工作(箭头表示虚拟继承,而这些行表示非虚拟继承)。

std::exception     std::exception 
     +        + 
     |        | 
     |        | 
     +        + 
std::runtime_error    std::runtime_error 
     +        + 
     |        | 
     | +-->boost::exception<-+  | 
     + |      |  + 
    foo_error+<-----+   +--->+bar_error 
        |   | 
        |   | 
        |   | 
        +abc_error+ 

它看起来像abc_error包括std::exception两个实例所以catch(或因此我认为)应该不能投abc_errorstd::exception。还是应该?

UPDATE

我不能回答,此刻我自己的问题,所以我要在这里继续。我已经将问题缩小到:

struct NonVirtualBaseBase { }; 
struct NonVirtualBase : NonVirtualBaseBase { }; 

struct VirtualBase { }; 

struct A : virtual VirtualBase, NonVirtualBase { }; 
struct B : virtual VirtualBase, NonVirtualBase { }; 

struct C : A, B { }; 

int main() 
{ 
    try 
    { 
     throw C(); 
    } 
    catch (const VirtualBase& e) 
    { 
     return 1; 
    } 

    return 0; 
} 

上面的示例按预期方式工作,并且是完美的一段代码。如果我将catch (const VirtualBase& e)替换为catch (const NonVirtualBase& e),我认为它是理智的并且合理。 它也适用,如果我用catch (const NonVirtualBaseBase& e)替换同一行,这对我来说似乎是奇怪和错误的。一个编译器错误?

+0

对于格式良好的代码+1,很好的问题与解释和酷的ASCII艺术:) –

+0

这是哪个编译器? – Agentlien

+0

@Agentlien这是 Microsoft(R)C/C++优化编译器版本16.00.30319.01 x64 –

回答

2

UPDATE

正如OP指出的那样,这种解释并不完全切断它,因为std::exception不使用虚拟继承的。我相信答案是这实际上是未定义的行为,并且不会在编译时被捕获,因为不需要throwcatch彼此了解并警告它们是否不兼容。

结束时更新

答案是,这个层次使用* 虚拟继承 *从boost::exception派生。

由于两个foo_errorbar_error使用虚拟继承来从boost::exception继承,只会有其两个foo_errorbar_error一个abc_error的子对象之间共享的单个boost::exception碱。

当您在基类列表中的条目前面指定virtual时,这意味着该类作为大多数派生对象中的虚拟基类的所有出现将实际上指向相同的实例。它被专门用于避免这种类型的设计中的歧义。

+0

是的,我不会问我是否试图捕获'boost :: exception'。但是我试图'catch''std :: exception',而不是'boost :: exception',它包含在非虚拟继承中。 –

+0

@EgorTensin我明白了,现在..这是一个很好的观点。 – Agentlien

+0

对不起,这只是我无法在我的脑海中得到这个直接!一个'abc_error'实例包含多少个'std :: exception'实例?我认为它是2,一个来自'foo_error',另一个来自'bar_error'。因此,转换为'std :: exception'应该是不明确的。我知道转换为'boost :: exception'是可以的,因为'abc_error'实例只包含'boost :: exception'的一个实例(因为它继承了'virtual'ly,不像'std :: runtime_error')。 –

1

为什么这样吗?

对于“工作”的一些松散定义的值。

[email protected] > g++ -o test test.cpp 
[email protected] > ./test 
terminate called after throwing an instance of 'abc_error' 
Aborted (core dumped) 
[email protected] > 
+0

奇怪...我得到了'> main.exe abc错误''cl/I之后“C:\ boost \ boost_1_55_0”main.cpp' –

+0

我也从cl得到了这个。奇怪它不是:) –

1

它来编译,因为没有任何理由,你不能有例外多重定义的基类,并没有理由你不能有一个异常声明基类的地方。事实上,abc碰巧抛出一个特定的东西,而那main碰巧抓住一个特定的事情,而这些东西是敌我,不能被编译时检查。

它不会做的是正确地捕捉异常,或者至少它不应该。但是我可以看到特定的编译器如何最终(错误地)这样做,因为异常声明是如何匹配的。