为什么我需要使用reinterpret_cast将Fred ** const转换为void ** const?

12

我有一个指向 Fred 指针的常量指针,但我不明白为什么 static_cast 不足以满足要求。

typedef struct {
    int n;
} Fred;

Fred *pFred;
Fred **const ppFred = &pFred;
void **const ppVoid = static_cast<void ** const>(ppFred);
请问为什么需要使用reinterpret_cast将指向Fred*的指针转换为指向void*的指针,但是使用static_cast将指向Fred的指针转换为指向void的指针就可以了呢?

1
有一个 void** 有意义吗? - moooeeeep
我正在编写一个C风格的回调消息接口,并接收一个指向结构体的指针。相同的C调用用于获取所有结构体,因此我不确定是否有其他选项。 - DangerMouse
类似于http://stackoverflow.com/questions/11409289/c-comparison-of-array-of-pointers,尽管那是C语言,而且其他提问者也在询问如何解决他们的情况。 - Steve Jessop
@moooeeeep void** 是指向 void* 的指针,它可以用于在 C 语言中将 void* 作为“引用传递”传递给函数。realloc 接口也可以这样定义。我在这里并不是要提倡这个想法,只是说这是一种可能性。 - curiousguy
@DangerMouse,“static_cast<void ** const>”,你知道这个const没有任何作用或影响,对吧? - curiousguy
4个回答

12
并没有要求 Fred*void* 拥有相同的大小和表示方式(我曾在一些机器上工作时遇到过不同,但那是在我学习 C++ 之前)。当将 Fred* 转换为 void* 时,你会得到一个新的指针,它可能具有不同的大小和表示方式,但并没有关于 void* 所指向对象的大小和表示方式的信息。你只知道它是未知的,使用这个 void* 的唯一方法是将其转换回 Fred*(除了像 cv 限定符之类的东西)。当将 Fred** 转换为 void** 时,你正在将一个指向具体类型(Fred* 指针)的指针转换为另一种具体类型(指向 void* 的指针),由于这两种具体类型的大小和表示方式没有保证相同,所以转换需要使用 reinterpret_castvoid 是一种特殊的、非具体的类型,因此你可以将任何类型的指针与指向 void 的指针进行 static_cast。而 void* 只是另一种具体的指针类型,因此转换指向它的指针与从它到其他指针的转换都遵循通常的规则(需要使用 reinterpret_cast)。
在许多方面,情况非常类似于 intdouble,其中 void* 扮演 int 的角色(比如),而 Fred* 扮演 double 的角色。在 intdouble 之间进行 static_cast 没有任何问题,但是在 int*double* 之间进行转换需要使用 reinterpret_cast

那么我可以做 static_cast<void * const>(&pFred) 吗? - DangerMouse
"void* 扮演 int 的角色(比如说),而 Fred* 扮演 double 的角色。反过来也一样,因为通常每个 int 值都可以表示为 double,而每个 Fred* 值都可以表示为 void。X 转换为 Y 的事实有时会让人误以为 X 可能会转换为 Y*,我认为这是一个误区。" - Steve Jessop
1
@SteveJessop 是的。当我想到这个例子时,我没有考虑到这个方面。我的想法只是你可以在两个“值”之间进行转换,但你不能在两个指向这些值的指针之间进行转换。 - James Kanze
@DangerMouse 是的,但您无法取消引用该指针。唯一合法的方法是将其强制转换回 Fred*(模除 cv 限定符),并使用它来进行取消引用操作。 - James Kanze
1
@Chethan 实际上是历史原因。在某些情况下,仍然有必要指向某个东西,而不知道它是什么(尽管在最早的C语言中,这是char*)。人们还同意将T*转换为该指针(现在为void*)可以是隐式的。后来,决定任何隐式转换的反转应该是一个static_cast。当然,所有这些都不适用于void**,因为void**并不指向任何东西;它指向一个具体的数据类型,即void* - James Kanze
显示剩余3条评论

3

所有对象指针都可以转换为 void*, 因此静态类型转换是可以的。然而,在一般情况下,T*U* 之间的转换需要重新解释转换,因为任意指针不能互相转换。(并将 T = Fred*U = void* 替换进去。)


豁免转换为void*的理由是什么?为什么这不适用于void**? - Chethan
@Chethan:void*是特殊的。每个指针都可以转换为void* - Kerrek SB

3
static_cast不能把Fred **转换为void **,因为这不是一个合理的转换:指向Fred*void*的指针在某些平台上可能没有以相同的方式创建(例如,对齐问题)。你可以确定一个能够指向内存中任何字节的void*也可以指向一个Fred对象,但对于void**指针来说并非如此。

如果我理解正确的话,您是在说指向void的指针可能与指向Fred的指针不同,但指向void的指针可以指向任何东西?因此,在函数签名中,我可以像使用void*一样轻松地使用void - DangerMouse

1

免责声明

以下是为了让事情更容易理解而进行的手势,并非技术上正确的描述。

手势

介绍 void 的一种可能方式是:

void 与 Java 的 Object 通用超类类似(但不完全相同)。

void 可以被看作是每个类和非类类型的抽象基类。(使用这个比喻,void 也将成为准虚拟基类:转换为 void* 永远不会有歧义。)

因此,您可以将从 T*void* 的隐式转换视为派生到基类的转换,而反向的 static_cast 就像是从基类到派生类的向下转换。当 void* 实际上并没有指向 T 时,您不应该执行 static_cast<T*>(当 Base* 实际上并没有指向 Derived 时,您不应该执行 static_cast<Derived*>)。

免责声明,再次提醒

严肃地说,void不是抽象基类,在许多情况下不能正式地被视为抽象基类:

  • 您不能将void正式描述为虚基类(否则static_cast会出错),也不能将其描述为非虚基类(否则在使用多重继承时,转换为void*会产生歧义)。
  • 没有void&类型。这个基类的“隐喻”不仅限于指针。

请不要告诉别人“void是像Java Object一样的通用C++基类”。不要在没有完整免责声明的情况下重复我在这里写的任何内容。

只有在某些情况下,void才会像基类一样行事,用于指针隐式转换和强制转换。

您不能基于隐喻编写程序,而是要遵循真正的C++规则。

这个隐喻可能有所帮助。或者不是。无论如何,不要试图根据隐喻得出逻辑结论。


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