向下转型和向上转型是如何进行的?以及如何比较类型? RTTI 通常是如何存储的?
How downcasts and upcasts are preformed? And how the types are compared? How RTTI is usually stored?
我读过 RTTI
。这里写的信息可能是错误的。这就是我的理解。
1 - 每种类型都有一个指向其基 classes 的指针和一个指向包含其名称的字符串的指针。例如,当使用 dynamic_cast
进行转换时,它会遍历基础 classes 并递归直到找到一个匹配的。假设我所说的是真的,那么向上转型呢?它是如何完成的,因为每种类型只知道它的基础 classes,它如何弄清楚它是 subclasses?
2 - 在实际转换之前它是否知道操作是向下转换还是向上转换?换句话说,当我执行 dynamic_cast<SomeClass>
时,它会尝试在整个层次结构树中找到 SomeClass
吗?或者它是否知道去哪个方向(在父节点中搜索或在子节点中搜索)?如果是,那又如何?
3 - 据我所知,每个 class 的类型都存储为字符串,每当有人使用 dynamic_cast
时,它都会比较类型的字符串,直到找到正确的class。如果这是真的,为什么要这样做?为什么不在编译时给每个 class 一个整数 ID 并存储该 ID 而不是字符串名称。每当发生转换时,只需比较两个数字。并让所有 classes 的类型字符串存储在某处的数组中(我们称之为 typesArr
),无论何时实际需要获取 class 的名称,只需查找 typesArr[ID]
.我认为这样的东西更直观,我遗漏了一些东西。那么 RTTI
实际上是如何存储的呢?我不是说它是如何工作的。我的意思是它在内存中是如何表示的?我知道这取决于实现。但是在大多数编译器中它通常是如何存储的呢?类型实际上是如何比较的?
(强制免责声明:正如问题中已经指出的,其中大部分是特定于实现的,而不是一般的 C++ 规则。)
我认为您可能正在使用与我习惯相反的 "upcast" 和 "downcast"。
但在任何情况下,从派生 class 指针到基 class 指针的转换(或从派生 class 泛左值初始化基 class 引用)根本不需要涉及 RTTI "class tree"。编译器知道所有基 classes 和派生 class 中子对象的布局。对于非虚拟基,基子对象地址位于派生对象地址的固定偏移处。对于虚拟基础 class,基础子对象的偏移量取决于对象的最派生类型,因此转换涉及在 vtable 中查找该偏移量。
dynamic_cast
定义为如果有效 ([expr.dynamic.cast]/5),则只执行上述派生到基础的转换。否则,是的,它在对象的完整类型继承的 classes 的整个树中搜索基础 class 。这个搜索的实现可能会从根开始:最派生的class。请注意,派生到基和基到派生并不是唯一的情况:dynamic_cast
也可以转换 "sideways",以找到 sibling/cousin/etc。子对象。
struct A { virtual ~A(); int m; };
struct B { virtual ~B(); int n; };
int f(const A& a) {
// Valid, even though there's no inheritance relation between
// A and B at all:
auto& b = dynamic_cast<const B&>(a);
return b.n;
}
struct C : public A, public B { int p; };
void g() {
C c;
c.n = 2;
// The dynamic_cast in f will be a successful "sideways cast"
// from the A base subobject of c to the B base subobject of c.
assert(f(c) == 2);
}
As far as I've understood, The type of each class is stored as a string and whenever someone uses dynamic_cast
, it compares the strings of types until it finds the right class. If that's true, why this is done? Why not giving each class an integer ID at compile time and storing that ID instead of the string name. And whenever casting happens, just compare the two numbers.
由于 C++ 使用的独立编译模型,这不会很容易实现。假设 Alice 编译了她的文件 a.cpp,其中定义了一些多态的 classes。编译器需要为那些 classes 选择一些 ID。同时,Bob 是 NiftyLib 的开发人员,他添加了一项新功能,这意味着 NiftyLib 源中的文件 b.cpp 具有一些新的多态性 classes。他的功能已准备好发布,因此库项目将 b.cpp 和其他源代码编译成库文件,供开发人员使用。这将意味着为那些 classes 选择一些 ID。 Alice 的 a.cpp 是使用 NiftyLib 的程序的一部分,因此她升级到较新的 NiftyLib 版本。制作 Alice 的完整程序涉及链接之前编译的 a.cpp 和 NiftyLib 库文件。但是那些编译器怎么会选择唯一的 ID,使得来自 a.cpp 和 b.cpp 的 class 中的 none 碰巧共享相同的 ID?
所以我认为 dynamic_cast
的某些实现确实比较了通过 RTTI 找到的一些损坏的类型名称,可能是 std::type_info::name()
返回的相同 C 字符串数据。但不是所有的。在 Itanium ABI 系统上(见下文),编译器和链接器可以进行设置以保证每种类型只有一个 RTTI 数据对象(也是 std::type_info
对象),即使重复的对象最初是从不同的对象发出的翻译单位。然后当代码请求 dynamic_cast<T*>(ptr)
时,编译器会将 T
的已知 RTTI 对象和通过 *ptr
中的 vptr 获得的 RTTI 对象传递给实现 dynamic_cast
的内部支持函数。当该函数正在搜索链接的 RTTI 对象树时,它可以只比较 RTTI 对象的地址,而不是检查它们的任何内容是否匹配。
有关大量技术细节,您可以查看 Itanium C++ ABI,用于 Linux、Mac 和其他几个平台。特别是,第 2.9 节是关于 RTTI 的,第 2.9.4 节指定了 RTTI 对象的所有内容,第 2.9.7 节描述了如何使用该数据来实现 dynamic_cast
(在真正动态的情况下)。上次看的时候,MSVC使用的RTTI data scheme 很相似,只是细节不同
我读过 RTTI
。这里写的信息可能是错误的。这就是我的理解。
1 - 每种类型都有一个指向其基 classes 的指针和一个指向包含其名称的字符串的指针。例如,当使用 dynamic_cast
进行转换时,它会遍历基础 classes 并递归直到找到一个匹配的。假设我所说的是真的,那么向上转型呢?它是如何完成的,因为每种类型只知道它的基础 classes,它如何弄清楚它是 subclasses?
2 - 在实际转换之前它是否知道操作是向下转换还是向上转换?换句话说,当我执行 dynamic_cast<SomeClass>
时,它会尝试在整个层次结构树中找到 SomeClass
吗?或者它是否知道去哪个方向(在父节点中搜索或在子节点中搜索)?如果是,那又如何?
3 - 据我所知,每个 class 的类型都存储为字符串,每当有人使用 dynamic_cast
时,它都会比较类型的字符串,直到找到正确的class。如果这是真的,为什么要这样做?为什么不在编译时给每个 class 一个整数 ID 并存储该 ID 而不是字符串名称。每当发生转换时,只需比较两个数字。并让所有 classes 的类型字符串存储在某处的数组中(我们称之为 typesArr
),无论何时实际需要获取 class 的名称,只需查找 typesArr[ID]
.我认为这样的东西更直观,我遗漏了一些东西。那么 RTTI
实际上是如何存储的呢?我不是说它是如何工作的。我的意思是它在内存中是如何表示的?我知道这取决于实现。但是在大多数编译器中它通常是如何存储的呢?类型实际上是如何比较的?
(强制免责声明:正如问题中已经指出的,其中大部分是特定于实现的,而不是一般的 C++ 规则。)
我认为您可能正在使用与我习惯相反的 "upcast" 和 "downcast"。
但在任何情况下,从派生 class 指针到基 class 指针的转换(或从派生 class 泛左值初始化基 class 引用)根本不需要涉及 RTTI "class tree"。编译器知道所有基 classes 和派生 class 中子对象的布局。对于非虚拟基,基子对象地址位于派生对象地址的固定偏移处。对于虚拟基础 class,基础子对象的偏移量取决于对象的最派生类型,因此转换涉及在 vtable 中查找该偏移量。
dynamic_cast
定义为如果有效 ([expr.dynamic.cast]/5),则只执行上述派生到基础的转换。否则,是的,它在对象的完整类型继承的 classes 的整个树中搜索基础 class 。这个搜索的实现可能会从根开始:最派生的class。请注意,派生到基和基到派生并不是唯一的情况:dynamic_cast
也可以转换 "sideways",以找到 sibling/cousin/etc。子对象。
struct A { virtual ~A(); int m; };
struct B { virtual ~B(); int n; };
int f(const A& a) {
// Valid, even though there's no inheritance relation between
// A and B at all:
auto& b = dynamic_cast<const B&>(a);
return b.n;
}
struct C : public A, public B { int p; };
void g() {
C c;
c.n = 2;
// The dynamic_cast in f will be a successful "sideways cast"
// from the A base subobject of c to the B base subobject of c.
assert(f(c) == 2);
}
As far as I've understood, The type of each class is stored as a string and whenever someone uses
dynamic_cast
, it compares the strings of types until it finds the right class. If that's true, why this is done? Why not giving each class an integer ID at compile time and storing that ID instead of the string name. And whenever casting happens, just compare the two numbers.
由于 C++ 使用的独立编译模型,这不会很容易实现。假设 Alice 编译了她的文件 a.cpp,其中定义了一些多态的 classes。编译器需要为那些 classes 选择一些 ID。同时,Bob 是 NiftyLib 的开发人员,他添加了一项新功能,这意味着 NiftyLib 源中的文件 b.cpp 具有一些新的多态性 classes。他的功能已准备好发布,因此库项目将 b.cpp 和其他源代码编译成库文件,供开发人员使用。这将意味着为那些 classes 选择一些 ID。 Alice 的 a.cpp 是使用 NiftyLib 的程序的一部分,因此她升级到较新的 NiftyLib 版本。制作 Alice 的完整程序涉及链接之前编译的 a.cpp 和 NiftyLib 库文件。但是那些编译器怎么会选择唯一的 ID,使得来自 a.cpp 和 b.cpp 的 class 中的 none 碰巧共享相同的 ID?
所以我认为 dynamic_cast
的某些实现确实比较了通过 RTTI 找到的一些损坏的类型名称,可能是 std::type_info::name()
返回的相同 C 字符串数据。但不是所有的。在 Itanium ABI 系统上(见下文),编译器和链接器可以进行设置以保证每种类型只有一个 RTTI 数据对象(也是 std::type_info
对象),即使重复的对象最初是从不同的对象发出的翻译单位。然后当代码请求 dynamic_cast<T*>(ptr)
时,编译器会将 T
的已知 RTTI 对象和通过 *ptr
中的 vptr 获得的 RTTI 对象传递给实现 dynamic_cast
的内部支持函数。当该函数正在搜索链接的 RTTI 对象树时,它可以只比较 RTTI 对象的地址,而不是检查它们的任何内容是否匹配。
有关大量技术细节,您可以查看 Itanium C++ ABI,用于 Linux、Mac 和其他几个平台。特别是,第 2.9 节是关于 RTTI 的,第 2.9.4 节指定了 RTTI 对象的所有内容,第 2.9.7 节描述了如何使用该数据来实现 dynamic_cast
(在真正动态的情况下)。上次看的时候,MSVC使用的RTTI data scheme 很相似,只是细节不同