考虑这个例子:
struct Nobody_Expects_The_Spanish_Inquisition{};
int main(){
throw Nobody_Expects_The_Spanish_Inquisition();
}
Ideone:上显示的输出
抛出Nobody_Expects_The_Spanish_Inquisition'实例后调用terminate
Windows的类似输出:
Test.exe中0x760fb727处未处理的异常:Microsoft C++异常:内存位置0x001ffea3处的Nobody_Expects_The_Spanish_Iquisition。
可以看出,最终程序集似乎已经包含异常的名称,或者还有其他方法可以获取该名称。
这能被看作是某种反思吗?或者是否可以实际显示异常的名称取决于编译器/OS?
它依赖于编译器。显然,编译器很容易发现每个抛出,并将每个抛出对象的类型编码到可执行文件中。但没有要求他们这样做。
仔细想想,异常在抛出时必须复制到一个奇怪的依赖于实现的空间中。因此,通过这种机制,特定编译器的运行时可以访问它们类型的名称,这是有道理的。
不,它不是任何有意义的能力的反映,只是调试符号。
当异常转义main
时,会调用std::terminate
,进而调用已安装的终止处理程序。如果程序中没有设置终止处理程序,则调用默认的终止处理程序。此默认终止处理程序的唯一要求是它调用std::abort
。这意味着一个实现可以在调用std::abort
之前免费打印一条有用的消息,这里显然就是这样。
虽然反射或调试符号或RTTI足以打印此错误消息,但它们不是必需的:实现可以使用任何类型的黑魔法,无论多么深入。
此程序:
#include <typeinfo>
#include <iostream>
struct Nobody_Expects_The_Spanish_Inquisition {
};
namespace junk {
struct I_didnt_expect_a_kind_of_spanish_inquisition
: public Nobody_Expects_The_Spanish_Inquisition
{
};
}
int main(int argc, const char *argv[])
{
using ::std::type_info;
using ::std::cout;
Nobody_Expects_The_Spanish_Inquisition foo;
junk::I_didnt_expect_a_kind_of_spanish_inquisition bar;
const type_info &fooinfo = typeid(foo);
const type_info &barinfo = typeid(bar);
cout << "The type of foo is <" << fooinfo.name() << ">n";
cout << "The type of bar is <" << barinfo.name() << ">n";
return 0;
}
具有以下输出:
$ ./foo
The type of foo is <38Nobody_Expects_The_Spanish_Inquisition>
The type of bar is <N4junk44I_didnt_expect_a_kind_of_spanish_inquisitionE>
这与C++中的内省一样好。这几乎不足以完成默认的终止处理程序所做的工作。
虽然,正如其他人所指出的,默认的终止处理程序可以随心所欲地实现这一目标,但如果它没有使用用于实现typeid
的相同机制来实现这一点,我会感到惊讶。
当然,默认的终止处理程序可以通过访问编译器在抛出异常时创建的一个特殊区域来工作,该区域记录了编译器在抛出该异常的地方对类型名称的了解。正如其他人所指出的,默认的终止处理程序是由编译器放在那里的,不受C++程序员编写的代码必须遵循的任何规则的约束。
我见过人们编写自己的终止处理程序,手动遍历调用堆栈,查找与每个地址相关的调试符号,以获得堆栈跟踪的一些传真。这些都是编译器和特定于平台的魔法,由于编译器确切地知道它是哪个编译器以及在哪个平台上使用,因此它可以有一个默认的终止处理程序,该处理程序在支持的平台上执行相同的操作。
总之,不需要RTTI来实现您注意到的功能。但是RTTI是一种非常基本的反射形式,可以用来实现该特性。
使用RTTI进行"反射"。没有什么花哨的,只是类型的名称。