为什么要在C ++中通过引用传递指针?


98

在哪种情况下,您想在C ++中使用这种性质的代码?

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

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

如果您需要返回指针-最好使用返回值
littleadv 2012年

6
你能解释为什么吗?这个推荐不是很有帮助。我的问题很合法。生产代码中当前正在使用此代码。我只是不完全明白为什么。
马修·霍根

1
大卫很好地总结了一下。指针本身正在被修改。
克里斯,2012年

2
如果要在函数中调用“ New”运算符,则可以通过这种方式。
NDEthos

Answers:


143

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

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


14
如此类似于指针的指针,只是传递引用语义的间接层少了一层?

我想补充一点,有时您还想通过引用返回一个指针。例如,如果您有一个在动态分配的数组中保存数据的类,但您想向客户端提供(非恒定)对此数据的访问。同时,您不希望客户端能够通过指针操作内存。

2
@威廉你能详细说明吗?听起来很有趣。这是否意味着该类的用户可以获取指向内部数据缓冲区的指针并对其进行读取/写入,但是它不能(例如)使用delete该指针释放该缓冲区或将其重新绑定到内存中的其他位置?还是我弄错了?
BarbaraKwarc '16

2
@BarbaraKwarc当然。假设您有一个动态数组的简单实现。自然,您会使[]操作员超负荷。一种可能的(实际上是自然的)签名是const T &operator[](size_t index) const。但是你也可以T &operator[](size_t index)。您可以同时拥有两个。后者会让您做类似的事情myArray[jj] = 42。同时,您没有提供指向数据的指针,因此调用者无法弄乱内存(例如,意外删除它)。

哦。我以为您是在谈论返回对指针的引用(您的确切措辞:“按引用返回指针”,因为那是OP所要的:通过引用传递指针,而不仅仅是普通的旧引用),作为一种奇特的方式提供对缓冲区的读/写访问权限,而不允许操纵缓冲区本身(例如,释放缓冲区)。返回简单的旧引用是另一回事,我知道。
BarbaraKwarc '16

65

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

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

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


19
我喜欢这个名字; )
2014年

2
我会为解决答案而愚蠢的回答:为什么叫moronic delete?我想念什么?
Sammaron

1
@Sammaron如果查看历史记录,该函数模板曾经被调用paranoid_delete,但是Puppy将其重命名为moronic_delete。他可能属于其他50%的用户;)总之,简而言之,就是将指针设置为after之后delete几乎是没有用的(因为它们无论如何都应该超出范围),并且常常会妨碍对“释放后使用”错误的检测。
fredoverflow

@fredoverflow我了解您的回应,但@Puppy似乎认为delete总体而言是愚蠢的。小狗,我做了一些谷歌搜索,看不到为什么删除是完全没用的(也许我也是一个可怕的谷歌用户;)。您可以多解释一下还是提供链接?
Sammaron

@ Sammaron,C ++现在具有“智能指针”,可以封装常规指针,并在它们超出范围时自动为您调用“删除”。智能指针比C风格的哑指针更受青睐,因为它们完全消除了此问题。
tjwrona1992

14

David的答案是正确的,但是如果仍然有点抽象,请参见以下两个示例:

  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(...);
        ...
    }

这个问题有一个例子。


8

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

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

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


2

一个示例是当您编写一个解析器函数并向其传递要读取的源指针时,如果该函数应该将该指针向前推到解析器已正确识别的最后一个字符之后。通过使用对指针的引用,可以清楚地知道该函数将移动原始指针以更新其位置。

通常,如果要向函数传递指针,并让其将原始指针移至其他位置,而不是仅移动其副本而不影响原始指针,则可以使用对指针的引用。


为什么要下票?我写的东西有问题吗?介意解释吗?:P
BarbaraKwarc

0

您可能需要这样做的另一种情况是,如果您拥有指针的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)签名,则具有指针的副本,而不是原始副本。


这比“更改对象”更像是“替换对象”-有所不同
serup
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.