通过引用强制转换指针

时间:2015-06-15 00:02:40

标签: c++ casting parameter-passing pass-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;
}

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

4 个答案:

答案 0 :(得分:4)

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

在第二个例子中,第一个reinterpret_cast返回一个指针(按值),它与函数应该得到的引用不匹配,而第二个返回所述引用。

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

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

虽然它可能看起来像是按值传递,但函数会引用它,因此它将作为参考传递。

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

话虽如此,使用void *还有更好的替代方案,特别是void *&amp ;. void *使代码更难阅读,更容易用脚射击自己(如果有的话,让自己使用这些奇怪的演员阵容)。

正如我在评论中所说的那样,你可以使用模板,而不用担心虚空铸造。

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

或者,

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

编辑:旁注,你的第二个例子是缺少分号,

unsigned char* buffer = 0; // Right here

答案 1 :(得分:1)

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

b是一个指针,doStuff(b)正在接收指针的地址。类型匹配,b属于void*&类型(*b类型为void*),而且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*&

不同

答案 2 :(得分:1)

这就是如何直接将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 *&amp; (对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 *以获取更新的值。在实际实现中,所有指针都具有相同的大小,因此在类型之间切换时,您仍然可以避免这种操作。

但是,如果您使用重新解释的指针开始操作指向的值,则可能发生各种不良情况。您也可能违反了“严格别名”规则,因此您也可以将您的名字更改为Mister Undefined Behavior并加入马戏团。怪物。

答案 3 :(得分:0)

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

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

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

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

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

tl; dr:在投射时不要混合引用和值。对引用执行操作与对值执行操作不同;即使参数替换是隐含的。