【问题标题】:Casting a pointer by reference通过引用转换指针
【发布时间】:2023-03-24 13:20:01
【问题描述】:

我遇到了一些我不太理解的东西。假设我想将一个字符指针传递给一个引用 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;
}

这种行为背后一定有充分的理由,但我不明白。

【问题讨论】:

  • 考虑阅读模板。
  • 为什么会引用void 指针?这个例程可能会做什么?
  • @BaummitAugen:可以重新分配吗?
  • @JonathanPotter 这听起来比realloc 还要糟糕。
  • 我认为真正的答案是reinterpret_cast 产生一个纯右值,它不能绑定到对非常量的引用。

标签: c++ casting parameter-passing pass-by-reference


【解决方案1】:
int main()
{
  unsigned char* buffer = 0;
  void* b = reinterpret_cast<void *>(buffer);
  doStuff(b);
  return 0;
}

b 是一个指针,doStuff(b) 正在接收指针的地址。类型匹配,bvoid*&amp; 类型(*bvoid* 类型)并且 doStuff 接收到 void*&amp; 类型的参数。


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*&amp;不一样

【讨论】:

    【解决方案2】:

    在第一个示例中,您实际上是在传递指针变量 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
    

    虽然看起来您是按值传递它,但该函数需要一个引用,因此它将作为引用传递。

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


    话虽如此,但使用 void* 尤其是 void*& 有更好的替代方法。 void* 使代码更难阅读,也更容易让你自责(如果有的话,就是让你自己使用这些奇怪的演员表)。

    正如我在 cmets 中所说,您可以使用模板而不必为 void 强制转换而烦恼。

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

    或者,

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

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

    unsigned char* buffer = 0; // Right here
    

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

      【讨论】:

      • 我发现倒数第二段的解释相当混乱。我不确定这是否有助于或误导普通读者......
      【解决方案4】:

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

      但是,如果您开始使用重新解释的指针来操作指向的值,则可能会发生各种不良情况。那时您也可能违反“严格别名”规则,因此您不妨将您的名字更改为未定义行为先生并加入马戏团。怪胎。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-06
        • 2014-07-27
        • 2023-03-03
        • 1970-01-01
        相关资源
        最近更新 更多