在C++中通过引用传递指针是否有好处?

Mat*_*coe 220 c++ pointers parameter-passing pass-by-reference

在C++中通过引用传递指针有什么好处?

最近,我看到一些例子选择通过指针传递函数参数而不是通过引用传递.这样做有好处吗?

例:

func(SPRITE *x);
Run Code Online (Sandbox Code Playgroud)

随叫随到

func(&mySprite);
Run Code Online (Sandbox Code Playgroud)

func(SPRITE &x);
Run Code Online (Sandbox Code Playgroud)

随叫随到

func(mySprite);
Run Code Online (Sandbox Code Playgroud)

Joh*_*itb 236

通过指针传递

  • 来电者必须拿地址 - >不透明
  • 可以提供0值来表示nothing.这可用于提供可选参数.

通过引用传递

  • 调用者只是传递对象 - >透明.必须用于运算符重载,因为指针类型的重载是不可能的(指针是内置类型).所以你不能 string s = &str1 + &str2;使用指针.
  • 可能没有0值 - >被调用的函数不必检查它们
  • 对const的引用也接受临时性:void f(const T& t); ... f(T(a, b, c));指针不能像那样使用,因为你不能使用临时的地址.
  • 最后但同样重要的是,引用更容易使用 - >错误的机会更少.

  • 我不同意"减少错误的机会".当检查呼叫站点并且读者看到"foo(&s)"时,立即清楚可以修改s.当您阅读"foo(s)"时,如果可以修改s,则一点都不清楚.这是错误的主要来源.也许某类错误的可能性较小,但总的来说,通过引用传递是一个巨大的错误来源. (42认同)
  • "透明"是什么意思? (23认同)
  • 通过指针传递也会提出"是否已转让所有权?" 题.参考不是这种情况. (7认同)
  • @ MichaelJ.Davenport - 在你的解释中,你建议使用"透明"来表示"显然调用者正在传递指针,但调用者传递引用并不明显".在约翰内斯的帖子中,他说"通过指针传递 - 来电者必须采取地址 - >不透明"和"通过参考传递 - 来电者只是通过对象 - >透明" - 这几乎与你说的相反.我认为Gbert90的问题"透明"是什么意思仍然有效. (3认同)
  • @ Gbert90,如果你在调用网站上看到foo(&a),你知道foo()采用指针类型.如果你看到foo(a),你不知道它是否需要参考. (2认同)
  • @MichaelJ.Davenport——这似乎不是约翰内斯帖子中使用“透明”的上下文。 (2认同)
  • 透明度意味着“其他人很容易看到所执行的操作”(维基百科)。调用站点的地址运算符使读者更清楚地知道该参数可能会被修改。我已经编辑了答案,让我们看看更改是否被接受。 (2认同)

Ada*_*eld 212

一个指针可以接收一个NULL参数,一个参数参数不能.如果您有可能想要传递"无对象",则使用指针而不是引用.

此外,通过指针传递允许您在调用站点显式地查看对象是通过值还是通过引用传递:

// Is mySprite passed by value or by reference?  You can't tell 
// without looking at the definition of func()
func(mySprite);

// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);
Run Code Online (Sandbox Code Playgroud)

  • 答案不完整.使用指针不会授权临时/提升对象的使用,也不会使用尖头对象作为类似堆栈的对象.并且当大多数时候应该禁止NULL值时,它会建议参数可以为NULL.阅读litb的答案以获得完整的答案. (17认同)
  • @JonWheelock:不,C 根本没有传递引用。`func(int& a)` 在任何版本的标准中都不是有效的 C。您可能不小心将文件编译为 C++。 (2认同)

Mic*_*urr 62

艾伦·霍鲁布的"足够的足球射门"列出了以下两条规则:

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers
Run Code Online (Sandbox Code Playgroud)

他列举了为什么将引用添加到C++的几个原因:

  • 它们是定义复制构造函数所必需的
  • 它们是操作员超载所必需的
  • const 引用允许您在避免复制的同时具有按值传递的语义

他的主要观点是参考不应该用作"输出"参数,因为在呼叫站点没有指示参数是参考还是值参数.所以他的规则是只使用const引用作为参数.

就个人而言,我认为这是一个很好的经验法则,因为当参数是输出参数时它会更清楚.然而,虽然我个人同意这一点,但我确实允许自己受到团队中其他人的意见的影响,如果他们争论输出参数作为参考(一些开发人员非常喜欢它们).

  • 我在该论证中的立场是,如果函数名称使得它完全明显,不检查文档,那么param将被修改,那么非const引用就可以了.所以我个人允许"getDetails(DetailStruct&result)".那里的指针引发了NULL输入的丑陋可能性. (8认同)
  • 我不明白这是多么误导 - 有时需要引用,有时候最佳实践可能会建议不使用它们,即使可以.语言的任何特征都可以这么说 - 继承,非成员朋友,运营商重载,MI等...... (4认同)
  • 这是误导.即使有些人不喜欢引用,它们也是语言的重要组成部分,应该像这样使用.这种推理就像是说不使用模板,你总是可以使用void*的容器来存储任何类型.阅读litb的回答. (3认同)

R. *_*ega 62

我喜欢"cplusplus.com"中一篇文章的推理.

  1. 当函数不想修改参数并且值很容易复制时传递值(int,double,char,bool等...简单类型.std :: string,std :: vector和所有其他STL容器不是简单的类型.)

  2. 当复制值很昂贵时传递const指针并且函数不想修改指向的值AND NULL是函数处理的有效预期值.

  3. 当复制值很昂贵时,通过非const指针传递并且函数想要修改指向的值AND NULL是函数处理的有效预期值.

  4. 当复制值很昂贵时,通过const引用,并且函数不想修改引用的值.如果使用指针,NULL将不是有效值.

  5. 当复制值很昂贵时,通过非连续引用,并且函数想要修改引用的值.如果使用指针,NULL将不是有效值.

  6. 在编写模板函数时,没有明确的答案,因为需要考虑的一些权衡超出了本讨论的范围,但足以说大多数模板函数通过值或(const)引用获取其参数但是因为迭代器语法类似于指针(星号到"取消引用"),任何期望迭代器作为参数的模板函数也会默认接受指针(并且不检查NULL,因为NULL迭代器概念具有不同的语法).

http://www.cplusplus.com/articles/z6vU7k9E/

我从中得到的是,选择使用指针或引用参数之间的主要区别在于NULL是否是可接受的值.而已.

毕竟,值是输入,输出,可修改等应该在关于函数的文档/注释中.


Mr.*_*Ree 9

澄清前面的帖子:


引用不是获取非空指针的保证.(虽然我们经常这样对待它们.)

虽然可怕的代码很糟糕,就像把你带到了破旧的代码后面,下面将编译并运行:(至少在我的编译器下.)

bool test( int & a)
{
  return (&a) == (int *) NULL;
}

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};
Run Code Online (Sandbox Code Playgroud)

我引用的真正问题在于其他程序员,以下称为IDIOTS,他们在构造函数中分配,在析构函数中解除分配,并且无法提供复制构造函数或operator =().

突然间,foo(BAR bar)foo(BAR & bar)之间存在着天壤之别.(调用自动按位复制操作.析构函数中的解除分配被调用两次.)

值得庆幸的是,现代编译器将获取同一指针的双重释放.15年前,他们没有.(在gcc/g ++下,使用setenv MALLOC_CHECK_ 0重新访问旧方法.)在DEC UNIX下,在同一内存中分配给两个不同的对象.那里有很多调试乐趣......


更实际的是:

  • 引用隐藏您正在更改存储在其他位置的数据.
  • 将Reference与Copied对象混淆起来很容易.
  • 指针显而易见!

  • 这不是函数或引用的问题.你违反了语言规则.取消引用空指针本身已经是未定义的行为."引用并不能保证获得非空指针.":标准本身就是这样.其他方式构成未定义的行为. (16认同)
  • 无论你对返回的引用做了什么,当你说'*i`时,你的程序都有未定义的行为.例如,编译器可以看到这段代码并假设"好吧,这段代码在所有代码路径中都有未定义的行为,因此整个函数必须是不可达的." 然后它将假定不采用导致此功能的所有分支.这是定期执行的优化. (2认同)

Tom*_* VH 6

就表达意图而言,这里的大多数答案都未能解决在函数签名中包含原始指针的固有歧义。问题如下:

  • 调用者不知道指针是指向单个对象,还是指向对象“数组”的开头。

  • 调用者不知道指针是否“拥有”它指向的内存。IE,该函数是否应该释放内存。(foo(new int)- 这是内存泄漏吗?)。

  • 调用者不知道是否nullptr可以安全地传入函数。

所有这些问题都通过参考解决:

  • 引用总是指向单个对象。

  • 引用从不拥有它们所引用的内存,它们只是对内存的一个视图。

  • 引用不能为空。

这使得引用更适合一般用途。然而,引用并不完美——有几个主要问题需要考虑。

  • 没有明确的间接。这不是原始指针的问题,因为我们必须使用&运算符来表明我们确实在传递一个指针。例如,int a = 5; foo(a);这里根本不清楚 a 是通过引用传递的并且可以被修改。
  • 可空性。当我们实际上希望我们的引用可以为空时,指针的这种弱点也可以成为一种优势。看到 asstd::optional<T&>无效(有充分的理由),指针为我们提供了您想要的可空性。

因此,当我们想要一个具有显式间接性的可为空引用时,我们似乎应该争取一个T*权利?错误的!

抽象

在我们对可空性的绝望中,我们可能会触及T*,并简单地忽略前面列出的所有缺点和语义歧义。相反,我们应该达到 C++ 最擅长的:抽象。如果我们简单地编写一个环绕指针的类,我们将获得表达能力,以及可空性和显式间接性。

template <typename T>
struct optional_ref {
  optional_ref() : ptr(nullptr) {}
  optional_ref(T* t) : ptr(t) {}
  optional_ref(std::nullptr_t) : ptr(nullptr) {}

  T& get() const {
    return *ptr;
  }

  explicit operator bool() const {
    return bool(ptr);
  }

private:
  T* ptr;
};
Run Code Online (Sandbox Code Playgroud)

这是我能想到的最简单的界面,但它可以有效地完成工作。它允许初始化引用,检查值是否存在并访问该值。我们可以像这样使用它:

void foo(optional_ref<int> x) {
  if (x) {
    auto y = x.get();
    // use y here
  }
}

int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
Run Code Online (Sandbox Code Playgroud)

我们已经实现了我们的目标!现在让我们看看与原始指针相比的好处。

  • 界面清楚地表明引用应该只引用一个对象。
  • 显然它不拥有它所指的内存,因为它没有用户定义的析构函数,也没有删除内存的方法。
  • 调用者知道nullptr可以传入,因为函数作者明确要求optional_ref

我们可以从这里开始使接口更复杂,例如添加相等运算符、一元get_ormap接口、获取值或抛出异常的方法、constexpr支持。这可以由你完成。

总之,与其使用原始指针,不如推理这些指针在您的代码中的实际含义,并且要么利用标准库抽象,要么编写您自己的。这将显着改进您的代码。