2016-11-24 59 views
8

假设我有以下两个文件,main.cpp编译器如何知道要使用哪个catch块?

#include <iostream> 

class A {};  
void foo(); 

int main(void) 
{ 
    try { 
     foo(); 
    } 
    catch(const A& e) { 
     std::cout << "Caught an A." << std::endl; 
    } 
    return 0; 
} 

foo.cpp

class A {}; 
class B : public A {}; 

void foo() 
{ 
    B b; 
    throw b; 
} 

现在,当我单独编译每个文件,链接生成的目标文件,并运行结果可执行文件,我得到预期的结果:

$ clang++ --std=c++14 -c main.cpp 
$ clang++ --std=c++14 -c foo.cpp 
$ clang++ --std=c++14 main.o foo.o 
$ ./a.out 
Caught an A. 

而这让我难以置信!类A没有虚拟方法。因此,它不是多形的,它的实例在运行时不应携带任何类型信息。 main.o目标文件不知道正在抛出什么,因为实际抛出发生在foo()内部,其主体在单独的编译单元中定义。目标文件具有更多信息,但同样不了解任何捕获语句和捕获的异常的预期类型。

简而言之:我没有看到两个源文件分别编译然后链接如何生成上面的输入,而无需处理一些运行时类型信息。这两个文件分别编译应该有足够的信息来采取正确的catch块。

+1

B是A的子类,因此您可以在B的任何实例上查看A的“透镜”。 – freestyle

+0

为什么要重新定义“A类”? – Raindrop7

+0

由于协变 – arturx64

回答

5

这当然完全依赖于编译器。

所有编译器的约束条件是:

  • 时抛出异常的类型是已知的(无论是在编译时,或在运行时的情况下你会抛出一个多态对象)。
  • 适用的catch块(它们可以是几个),它们的类型取决于执行路径。

这意味着类型必须在运行时被识别,即使异常对象是非plymorphic。

实现此目的的简单方法是将指针传递给typeinfo对象以及抛出的对象本身。这是GCC使用的方法:看online code,在这里foo()投掷方便的摘录:

call __cxa_allocate_exception 
    mov  edx, 0 
    mov  esi, OFFSET FLAT:typeinfo for B ; <== !! 
    mov  rdi, rax 
    call __cxa_throw 
+0

我明白了。我没有考虑到,仅仅因为实际的物体没有携带任何类型的信息并不意味着它们不能在投掷的地点被静态确定并且与物体一起传递。 – Witiko

+2

@Witiko这是一个非常有趣的问题。我生成的代码使'A'多态,认为它会使用RTTI。但是否:编译器继续传递其附加的'typeinfo'。这是因为捕获代码无法知道是否抛出了多态对象或非多态对象,因此需要统一的接口。 – Christophe

2

它的RTTI(运行时类型信息)的组合和实现特定的编码类型数据在尝试评估哪个catch块得到什么时生成比较类型。

但只是比较两种类型的面值可能不会产生正确的结果(例如对于基类和派生类)。在Windows的情况下,存在特殊的附加扩展类型信息结构(etype_info或类似的东西),其包含层次结构中需要遍历以确定潜在匹配的所有类(因为基类可以是限制视图进入派生类)。如果遍历一个匹配被发现,catch块被调用。

补遗

异常处理需要由OS提供特殊的运行时支持,因此出现这种情况的方法是实现定义(如Windows结构化异常处理),只要最终结果满足标准。

楼上是它的Windows风味的要点。

+0

RTTI如何发挥作用,那么,如果完整的类型信息与抛出的对象一起传递? – Witiko

+0

它提供了[type_info](http://en.cppreference.com/w/cpp/types/type_info),用于比较操作并且集成到异常信息结构中('excpt_info'或者沿着这些行的东西) 。在返回到catch块时展开堆栈,该结构被携带并用于测试catch块类型(其类型信息记录在编译时记录的另一个结构中,一旦它看到try-catch)。 –

+1

如果您想了解更多关于此主题的信息,您可能需要探索SEH之类的实现方式,它在网络上已有详细记录。一般RTTI也是如此。希望这可以帮助。 –

相关问题