为什么我要使用dynamic_cast将一个对象指针转换成void*?

14

我在阅读有关从"void *"动态转换的答案时,尽管您无法将void *转换为T *,但其中几个回答指出可以将T *转换为void *,但没有给出任何提示,为什么要这样做。

这只是一些可能性的琐事,还是有一种情况使这有意义?我想过可能是为了可读性或清楚地表明我们正在转换为void *,但考虑到dynamic_cast的目的,它对我来说并不太合适。

同样,除了让T *隐式成为void *之外,是否有任何理由做其他事情?我偶尔看到使用C风格转换为void *,我认为这只是为了明确(假设我们不像将int转换为指针或类似的非常规操作)。


据我所知,如果源类型是多态的,使用dynamic_cast<void *>()和使用隐式转换到void *的结果始终返回相同的结果。我想你可以像static_assert(is_polymophic(x))一样使用它,但我真的想不出一个好的理由去这样做。 - Vaughn Cato
3个回答

11

首先,使用dynamic_cast<void*>(x)将获得指向最终派生类对象的第一个字节的指针,只要x的静态类型具有多态性。

在一些场景中,这可能很有用,其中地址作为对象标识:

  • 您现在可以完全区分指向同一对象的子对象的指针与指向无关子对象的指针。
  • 现在可以遍历某些扭曲图形而不多次访问相同的对象...这可用于序列化

当然,这肯定不是日常使用,但在C++中,内存地址是对象的事实标识符,因此从继承层次结构的任何部分访问它的机制对于那些少数边缘情况确实是有用的。


2
如果我使用一个基类指针并将其转换为派生类,实际地址值可能会不同吗?如果这是个愚蠢的问题,对不起,我一直把它当作魔法来看待。;) - FatalError
4
在单继承的情况下,如果基类是多态的,那么地址实际上应该是相同的。但是,一旦开始使用多重继承或者如果基类不是多态的而派生类是多态的,则地址可能会有所不同。可以将基类视为第一个(隐藏的)属性-->毕竟它们是构造函数初始化列表中的第一个;因此,当存在多个类时,第二个基类的开头和当前派生类的开头不再相同(除非有例外)。 - Matthieu M.
2
我试了一下,你说得完全正确。当我使用多重继承时,我发现地址会随着dynamic_cast而改变,但不会随着static_cast或C风格的转换而改变。谢谢! - FatalError
请注意,这也有助于调用对象的(虚拟)成员函数,即使您可能没有足够派生的指针(在多重继承的情况下特别有用)。 - rubenvb
@rubenvb:抱歉,我不是很明白您的意思……您如何在“void *”上调用(虚拟或非虚拟)成员函数? 在执行dynamic_cast之后进行reinterpret_cast操作吗? - Matthieu M.
@MatthieuM。我正在存储指向类“trackable”的指针,我保证它们在层次结构中,与调用的函数无关,并与成员函数指针一起使用。调用接口确实通过const void*进行通用化。当我处于这种情况时,即我要调用一个类的成员函数,而该类的trackable不是第一个基类时,我发现我需要对我存储的trackable*进行dynamic_cast<const void*>以正确调用成员函数。 - rubenvb

6
有一定的目的性在里面,可以在规范的某个部分中暗示出来。根据N3337,第5.2.7节,第7段,它被允许。如果T是“cv void指针”,那么结果就是指向v指向的最派生对象的指针。 因此,dynamic_cast(...) 实际上是static_cast(dynamic_cast(...)) 的简写。这有点有用......不过算是缩短代码吧。
使其有用的难点在于你不知道 MostDerivedType 是什么。毕竟,它对于每个表达式来说都可能不同。所以,一旦你将其作为void*使用,你就没有办法将其安全地强制转换回来。如果你猜测MostDerivedType并只使用static_cast将其强制转换,而你是错误的,那么你就会进入未定义的行为领域。而如果你对该类型执行dynamic_cast(然后static_cast到void *),即使不是该类型,它也至少会返回NULL。
所以不,我认为它不是非常有用。如果你想在C++范围内生存并且不依赖潜在的未定义行为,那么就不是很有用。

如果你有两个对象指针,你并不一定知道它们是否指向同一个对象——在多重继承的情况下,它们可能指向同一个对象的不同子对象。要查看它们是否为同一个对象,请将它们都强制转换为void*,然后比较结果。Windows COM也有类似的规则:查询 IUnknown 接口,无论从该对象的哪个接口开始,你将得到相同的结果,表示为同一对象。 - Rob Kennedy
那并没有解释为什么 dynamic_cast 需要这样做。已经说过 static_cast 的工作方式相同。 - Nicol Bolas
@NicolBolas:static_cast 无法与虚继承一起使用。 - Matthieu M.
@MatthieuM.:根据第4.10节第2段,不是这样的。 "类型为“指向cv T”的prvalue,其中T是对象类型,可以转换为类型为“指向cv void”的prvalue。将“指向cv T”的结果转换为“指向cv void”的结果指向存储位置的起始位置,其中类型T的对象驻留,就像对象是类型T(即不是基类子对象)的最派生对象(1.8)。请注意,这与dynamic_cast使用的语言完全相同。 - Nicol Bolas
1
@NicolBolas:这只是简单的转换,static_cast更加有限。请参阅5.2.9 [expr.static.cast] (n3337)。static_cast针对指针特别限制于需要固定偏移量的转换(因此名称为“静态”,表示偏移量在编译时计算)。例如,如果*B既不是D的虚基类,也不是D的虚基类的基类,则允许使用static_cast<D&>(b) - Matthieu M.
显示剩余2条评论

1

当实现newdelete操作符的封装时,这将非常有用。问题在于,delete操作符允许我们使用指向多态对象基类的指针来释放内存。在这种情况下,包装器不会知道实际上正在释放哪个对象(在之前由包装器分配的所有对象中)。因此,我们使用dynamic_cast< void * >()作为对象的唯一标识-它可以转换为多态对象的最派生类型。这样可以跟踪当前通过该包装器分配的所有对象。这种包装器可用于内存分配跟踪和批处理释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接