通过引用转换指针

Casting a pointer by reference

我遇到了一些我不太明白的事情。假设我想将一个字符指针传递给一个引用 void 指针的函数。

void doStuff(void*& buffer)
{
  // do something
}

我通常会这样做:

int main()
{
  unsigned char* buffer = 0;
  void* b = reinterpret_cast<void *>(buffer);
  doStuff(b);
  return 0;
}

为什么不能直接将reinterpret_cast传递给函数?

int main()
{
  unsigned char* buffer = 0
  // This generate a compilation error.
  doStuff(reinterpret_cast<void *>(buffer));
  // This would be fine.
  doStuff(reinterpret_cast<void *&>(buffer));
  return 0;
}

此行为背后一定有充分的理由,但我没有看到。

int main()
{
  unsigned char* buffer = 0;
  void* b = reinterpret_cast<void *>(buffer);
  doStuff(b);
  return 0;
}

b是指针,doStuff(b)是接收指针的地址。类型匹配,bvoid*& 类型(*bvoid* 类型)并且 doStuff 接收 void*&.

类型的参数
int main()
{
  unsigned char* buffer = 0

  // This generate a compilation error.
  doStuff(reinterpret_cast<void *>(buffer));

  // This would be fine.
  doStuff(reinterpret_cast<void *&>(buffer));

  return 0;
}

第二次调用类似于上述函数的调用,以 b 作为参数。

第一次调用只是传递一个 void 指针。类型不一样,仔细看void*void*&

不一样

在第一个示例中,您实际上传递的是指针变量 b。所以它有效。

在第二个例子中,第一个 reinterpret_cast returns 一个指针(按值),它与函数应该获取的引用不匹配,而第二个 returns 表示参考。

作为向您展示引用如何工作的示例,请查看这两个函数,

void doSomething( unsigned char *ptr );
void doSomethingRef( unsigned char *&ptr );

假设我们有这个指针,

unsigned char *a;

两个函数的调用方式相同,

doSomething( a ); // Passing pointer a by value
doSomethingRef( a );// Passing pointer a by reference

虽然它看起来像是按值传递,但该函数采用引用,因此它将作为引用传递。

引用类似于指针,但必须用左值初始化且不能为空。


话虽如此,除了使用 void* 尤其是 void*& 之外,还有很多更好的选择。 void* 使代码更难阅读,更容易搬起石头砸自己的脚(如果有的话,让自己使用这些奇怪的转换)。

正如我在评论中所说,您可以使用模板而不必费心进行 void 转换。

template< class T > void doStuff( T *&buffer ) {
    ...
}

或者,

template< class T > T* doStuff( T* buffer ) {
    ...
}

编辑:附带说明,您的第二个示例缺少分号,

unsigned char* buffer = 0; // Right here

我不确定这是否正确,但是...

我相信匹配参数类型很简单:

void doStuff(void* buffer) {
    std::cout << reinterpret_cast<char*>(buffer) << std::endl;
    return;
}

您可以执行上述操作,int main() 将正确编译。

引用不同于值的副本——不同之处在于复制的值不一定需要存在于变量或内存中的某个位置——复制的值可能只是一个堆栈变量,而引用不应该指向过期值。一旦您开始使用引用和值语义,这一点就变得很重要。

tl;dr:转换时不要混合引用和值。对引用进行操作不同于对值进行操作;即使参数替换是隐式转换的。

这就是您如何直接将 reinterpret_cast 指定为函数参数,而不使用中间变量。正如其他人告诉您的那样,这是不好的做法,但我想回答您最初的问题。当然,这仅用于教育目的!

#include <iostream>

void doStuff(void*& buffer) {
    static const int count = 4;
    buffer = static_cast<void*>(static_cast<char*>(buffer) + count);
}

int main() {
    char str[] = "0123456789";
    char* ptr = str;
    std::cout << "Before: '" << ptr << "'\n";
    doStuff(*reinterpret_cast<void**>(&ptr));   // <== Here's the Magic!
    std::cout << "After:  '" << ptr << "'\n";
}

这里我们有一个指向名为 ptr 的 char 的指针,我们希望将其类型转换为 void*&(对 void 指针的引用),适合作为参数传递给函数 doStuff。

虽然引用的实现方式与指针类似,但它们在语义上更像是另一个值的透明别名,因此该语言不提供操作指针的灵活性。

诀窍是:取消引用的指针直接转换为相应类型的引用。

所以要获得对指针的引用,我们从指向指针的指针开始:

&ptr  (char** - a pointer to a pointer to char)

现在 reinterpret_cast 的魔力让我们离目标更近了:

reinterpret_cast<void**>(&ptr)  (now void** - a pointer to a void pointer)

最后添加取消引用运算符,我们的伪装完成:

*reinterpret_cast<void**>(&ptr)   (void*& - a reference to a void pointer)

这在 Visual Studio 2013 年编译良好。这是程序输出的内容:

Before: '0123456789'
After:  '456789'

doStuff 函数成功将 ptr 推进 4 个字符,其中 ptr 是一个 char*,通过引用作为 reinterpret_cast void* 传递。

显然,此演示起作用的原因之一是因为 doStuff 将指针转换回 char* 以获取更新后的值。在现实世界的实现中,所有指针都具有相同的大小,因此在类型之间切换时您可能仍然可以摆脱这种操作。

但是,如果您开始使用重新解释的指针来操纵指向的值,则可能会发生各种不良情况。那时你也可能违反了 "strict aliasing" 规则,所以你不妨将你的名字更改为 Mister Undefined Behavior 并加入马戏团。怪胎.