【问题标题】:Reason to Pass a Pointer by Reference in C++?在 C++ 中通过引用传递指针的原因?
【发布时间】:2012-05-01 16:20:29
【问题描述】:

在什么情况下你想在 c++ 中使用这种性质的代码?

void foo(type *&in) {...}

void fii() {
  type *choochoo;
  ...
  foo(choochoo);
}

【问题讨论】:

  • 如果你需要返回一个指针 - 最好使用返回值
  • 你能解释一下为什么吗?这个表扬不是很有帮助。我的问题很合理。这目前正在生产代码中使用。我只是不完全明白为什么。
  • 大卫总结得很好。指针本身正在被修改。
  • 如果我要在函数中调用“New”运算符,我会通过这种方式。

标签: c++ pointers reference


【解决方案1】:

如果您需要修改指针而不是指针指向的对象,您可能希望通过引用传递指针。

这类似于为什么使用双指针;使用对指针的引用比使用指针更安全。

【讨论】:

  • 如此类似于指针的指针,只是传递引用语义的间接级别少了一层?
  • 我想补充一点,有时您还希望通过引用返回指针。例如,如果您有一个在动态分配的数组中保存数据的类,但您想向客户端提供对该数据的(非常量)访问。同时,你不希望客户端能够通过指针来操作内存。
  • @William 你能详细说明一下吗?听起来很有趣。这是否意味着该类的用户可以获得指向内部数据缓冲区的指针并对其进行读/写,但它不能 - 例如 - 使用 delete 释放该缓冲区或将该指针重新绑定到内存中的某个其他位置?还是我弄错了?
  • @BarbaraKwarc 当然。假设您有一个动态数组的简单实现。你自然会重载[] 运算符。一个可能的(实际上是自然的)签名是const T &operator[](size_t index) const。但你也可以有T &operator[](size_t index)。你可以同时拥有两者。后者会让你做myArray[jj] = 42之类的事情。同时,您没有提供指向数据的指针,因此调用者不会弄乱内存(例如意外删除它)。
  • 哦。我以为您在谈论返回对指针的引用(您的确切措辞:“通过引用返回指针”,因为这就是OP所要问的:通过引用传递指针,而不仅仅是简单的旧引用)作为某种奇特的方式授予对缓冲区的读/写访问权限,而不允许操作缓冲区本身(例如释放它)。返回一个普通的旧参考是另一回事,我知道。
【解决方案2】:

50% 的 C++ 程序员喜欢在删除后将其指针设置为 null:

template<typename T>    
void moronic_delete(T*& p)
{
    delete p;
    p = nullptr;
}

如果没有引用,您只会更改指针的本地副本,不会影响调用者。

【讨论】:

  • 我会用听起来很愚蠢的方式来找出答案:为什么它被称为白痴删除?我错过了什么?
  • @Sammaron 如果你看历史,函数模板曾经被称为paranoid_delete,但Puppy 将其重命名为moronic_delete。他可能属于另外 50% ;) 无论如何,简短的回答是在 delete 之后设置指向 null 的指针几乎从来没有用过(因为它们应该超出范围,无论如何)并且经常妨碍检测“免费后使用” " 错误。
  • @fredoverflow 我理解您的回复,但@Puppy 似乎认为delete 通常很愚蠢。小狗,我做了一些谷歌搜索,我不明白为什么删除完全没用(也许我也是一个糟糕的谷歌用户;))。你能解释一下或提供一个链接吗?
  • @Sammaron,C++ 现在有“智能指针”,它封装了常规指针,并在它们超出范围时自动为您调用“删除”。智能指针比 C 风格的哑指针更受欢迎,因为它们完全消除了这个问题。
【解决方案3】:

大卫的回答是对的,但如果还是有点抽象,这里举两个例子:

  1. 您可能希望将所有已释放的指针归零以更早地发现内存问题。你会做的 C 风格:

    void freeAndZero(void** ptr)
    {
        free(*ptr);
        *ptr = 0;
    }
    
    void* ptr = malloc(...);
    
    ...
    
    freeAndZero(&ptr);
    

    在 C++ 中也可以这样做:

    template<class T> void freeAndZero(T* &ptr)
    {
        delete ptr;
        ptr = 0;
    }
    
    int* ptr = new int;
    
    ...
    
    freeAndZero(ptr);
    
  2. 在处理链表时——通常简单地表示为指向下一个节点的指针:

    struct Node
    {
        value_t value;
        Node* next;
    };
    

    在这种情况下,当您插入空列表时,您必须更改传入指针,因为结果不再是 NULL 指针。这是您从函数修改外部指针的情况,因此它的签名中会引用指针:

    void insert(Node* &list)
    {
        ...
        if(!list) list = new Node(...);
        ...
    }
    

this question中有一个例子。

【讨论】:

    【解决方案4】:

    我不得不使用这样的代码来提供函数来为传入的指针分配内存并返回其大小,因为我的公司使用 STL 对我“对象”

     int iSizeOfArray(int* &piArray) {
        piArray = new int[iNumberOfElements];
        ...
        return iNumberOfElements;
     }
    

    这不好,但指针必须通过引用传递(或使用双指针)。否则,如果按值传递,则会将内存分配给指针的本地副本,从而导致内存泄漏。

    【讨论】:

      【解决方案5】:

      一个例子是当你编写一个解析器函数并将一个源指针传递给它以读取时,如果该函数应该将该指针向前推到解析器正确识别的最后一个字符的后面。使用对指针的引用可以清楚地表明该函数将移动原始指针以更新其位置。

      一般来说,如果你想将一个指针传递给一个函数并让它移动那个 original 指针到其他位置,而不是仅仅移动它的副本而不影响原创。

      【讨论】:

      • 为什么投反对票?我写的有什么问题吗?介意解释吗? :P
      【解决方案6】:

      您可能需要的另一种情况是,如果您有 stl 指针集合并想要更改 他们使用stl算法。 c++98中for_each的例子。

      struct Storage {
        typedef std::list<Object*> ObjectList;
        ObjectList objects;
      
        void change() {
          typedef void (*ChangeFunctionType)(Object*&);
          std::for_each<ObjectList::iterator, ChangeFunctionType>
                       (objects.begin(), objects.end(), &Storage::changeObject);
        }
      
        static void changeObject(Object*& item) {
          delete item;
          item = 0;
          if (someCondition) item = new Object();
        } 
      
      };
      

      否则,如果您使用 changeObject(Object* item) 签名,则您有指针的副本,而不是原始的。

      【讨论】:

      • 这更像是一个“替换对象”而不是一个“更改对象”——这是有区别的
      猜你喜欢
      • 2020-03-14
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-11
      • 1970-01-01
      相关资源
      最近更新 更多