考虑这个例子:
struct Nobody_Expects_The_Spanish_Inquisition{};
int main(){
throw Nobody_Expects_The_Spanish_Inquisition();
}
Ideone上显示的输出:
在抛出 Nobody_Expects_The_Spanish_Inquisition 的实例后终止调用
Windows的类似输出:
Test.exe中0x760fb727处的未处理异常:Microsoft C ++异常: Nobody_Expects_The_Spanish_Inquisition位于内存位置0x001ffea3 ..
可以看出,最终的程序集似乎已经包含了异常的名称,或者有另一种获取名称的方法。
这可以被视为某种反思吗?或者,如果实际上可以显示异常的名称,它是否依赖于编译器/ OS?
答案 0 :(得分:7)
它依赖于编译器。显然,编译器很容易发现每个throw,并将每个抛出的对象的类型编码到可执行文件中。但并不要求他们这样做。
考虑到这一点,异常必须在抛出时复制到一个奇怪的依赖于实现的空间。因此,通过此机制可以访问特定编译器的运行时,可以访问其类型的名称。
答案 1 :(得分:3)
不,它不是任何有意义的容量反射,只是调试符号。
答案 2 :(得分:2)
当异常转义为main
时,将调用std::terminate
,然后调用已安装的终止处理程序。如果程序中没有设置终止处理程序,则调用默认的终止处理程序。此默认终止处理程序的唯一要求是它调用std::abort
。这意味着在调用std::abort
之前,实现可以自由地打印有用的消息,这显然就是这种情况。
虽然反射或调试符号或RTTI足以打印此错误消息,但它们不是必需的:实现可以使用任何类型的黑魔法,无论多深。
答案 3 :(得分:0)
这个程序:
#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 ++中的内省一样好。而这几乎不足以完成默认的终止处理程序正在做的事情。
正如其他人已经指出的那样,默认的终止处理程序是允许完成这个目标的,无论如何它非常令人高兴,如果它没有使用相同的机制来实现{{}我会感到惊讶1}}使这项工作。
当然,默认的终止处理程序可以通过访问编译器在抛出异常时创建的特殊区域来工作,该异常记录编译器在抛出它时所知道的类型名称。正如其他人所指出的那样,默认的终止处理程序由编译器放在那里,并且不受C ++程序员编写的任何规则代码的约束。
我见过人们编写自己的终止处理程序,它们手动遍历调用堆栈并查找与每个地址关联的调试符号,以获得堆栈跟踪的一些传真。这些是特定于编译器和平台的魔法,并且由于编译器确切地知道它是哪个编译器以及它正在使用什么平台,因此它可以有一个默认的终止处理程序,它在支持的平台上执行相同的操作。
总之,RTTI不需要实现您注意到的功能。但RTTI是一种非常基本的反射形式,可用于实现该功能。
答案 4 :(得分:-1)
使用RTTI进行“反射”。没什么好看的,只是这个类型的名字。