按引用传递 vs 指针参数

7
可能重复:
FAQ: 如何在C++中将对象传递给函数?
指针 vs 引用 大家好, 在c/c++中, 我们可以通过引用调用或传递对象的指针来传递对象。 例如: 我想创建一个函数,它将以字符串向量为输入,并输出包含每个字符串某些值的映射。该函数的返回值是bool,表示成功或失败。 函数(通过引用调用)
bool calculateSomeValue( vector<string> input, map<string, double>& result)
{
//// bla bla bla
return true/false;
}
函数(使用指针)
bool calculateSomeValue( vector<string> input, map<string, double>* result)
{
//// bla bla bla
return true/false;
}
哪个更好?有人对这两个选项的优缺点有什么想法吗?
提前感谢。

4
指向引用的指针?有趣...... @user619237:在StackOverflow上搜索“指针与引用”的区别,你会得到比你想要的更多的答案。:] - user541686
2
https://dev59.com/OHVD5IYBdhLWcg3wBm1g - sinek
2
你甚至不需要搜索,只需看一下右侧的“相关”部分 :) - sinek
2
使用异常可以使您正确地使用函数的返回值,以便,哦,我不知道,返回一个值 - GManNickG
1
@GMan:谁说这里的bool是成功/失败指示器(编辑:好吧,问题还是)-它可能很容易成为结果信息的某个部分,例如结果集是否包含inf或nan数字;-P。无论如何,有许多时候一个函数应该影响许多变量,在这种情况下,您要么开始创建愚蠢的小元组(在C++0x中变得更容易),痛苦的特定结构,或者具有多个非const按引用或指针传递的参数,但您无法将它们清洁地打包到结果中。 - Tony Delroy
显示剩余6条评论
7个回答

11

这是一个风格问题。在谷歌(参见Google C++ 风格指南),以下写法更受欢迎:

bool CalculateSomeValue(
    const vector<string>& input, map<string, double>* result);

这是因为使用指针需要在调用处显式使用取地址符号:

 CalculateSomeValue(input, &result);

与引用类型调用方式不同:

 CalculateSomeValue(input, result);

强制使用和号来修改参数,可以清楚地表示在调用点会发生什么。当使用引用时,需要查找每个函数的文档才能知道它是否有可能修改其输入参数。

然而,使用指针也有其缺点。特别是,使用指针意味着你将处理空指针的责任从调用者转移到函数中。也就是说,使用指针类型时,CalculateSomeValue需要检查 nullptr (或者需要明确说明它在调用者处要进行此检查),而使用引用类型是自说明的,清晰地表示需要非空引用。

对于这种特定情况,我个人强烈建议采用混合方法:

bool CalculateSomeValue(
   const std::vector<std::string>& input,
   Output<map<string, double>> result);

......其中Output<T>是由一个带有如下签名的函数创建的:

 template<typename T> Output<T> WriteTo(T& output_ref);

...并且Output<T>重载了操作符->*等。这基本上强制调用端明确输入将被改变,因为需要:

 CalculateSomeValue(input, WriteTo(result));

...与以下的方式相反:

 CalculateSomeValue(input, result);

同时获得引用的非空语义/语法。


1
在我看来,有一个值得注意的例外:const引用比const指针更受青睐。 - user541686
@Michael:糟糕,我的错——我完全忽略了“参数被修改”这个短语。 - user541686
@Michael:我也遵循这种做法并发现它很有用,尽管值得指出的是C++ FAQ lite不同意(http://www.parashift.com/c++-faq-lite/references.html#faq-8.6)。在其他情况下,即使变量不会被潜在修改,仍可能通过指针传递:关联不完美(尽管我仍然认为怀疑变化更好)。 - Tony Delroy
1
-1。谷歌的风格指南一直备受诟病,我认为这是正确的。这个规则绝对是无意义的,因为即使看到&result,我仍然不知道参数是map<string, double>*还是const map<string, double>*(请注意const)。__您必须查看函数的声明才能真正理解参数是如何传递的。__这可能被称为C++的缺陷,但这就是我们所拥有和必须面对的。请参阅https://dev59.com/lnI95IYBdhLWcg3w1BdN,了解我认为合理的有关参数传递的规则。 - sbi
@sbi,在const的情况下,我们会使用引用。 - Michael Aaron Safyan
显示剩余5条评论

1

如果参数不是可选的,我发现许多C++开发人员更喜欢通过引用传递。然而,许多人仅出于视觉提示而选择通过指针传递。这有点像C语言的遗留问题,在处理引用和指针的混合使用时会逐渐消失。

你可以假设引用不为空,但不能对指针做出同样的假设。在这种情况下,你需要引入前置条件支架/检查,以确保上游没有认为该参数是可选的——如果你是防御性编程的话。

个人而言,我更喜欢通过引用传递,并使用描述性的方法/函数/变量名称(如果只详细说明标签,则人们必须更频繁地查看文档)。这可以保持程序意图清晰,并避免额外的书面检查。get*update*可能比calculate*更明显。

使用引用是一致的、定义良好的,并且产生更简单的程序,因为你不需要处理混合变量/参数类型。

在这种情况下,你选择哪种方式并不会有显著的差异,只要在使用中保持一致即可。我使用的另一个提示是将修改后的参数放在同一位置。具体来说,它们位于常量参数之前。


0
bool calculateSomeValue( vector<string> input, map<string, double>&* result)

指向引用的指针?这甚至无法编译。第一个是正确的,只有正确的才是最好的!
struct A {};

void f(A & *a) {}

编译出现错误:

prog.cpp:7: error: 无法声明指向 'struct A&' 的指针

Ideone 示例: http://www.ideone.com/8PJA5


0

最佳解决方案并不存在。它因情况而异。

例如,在某些情况下,下一个可能更好:

map<string, double> calculateSomeValue( const vector<string> &input )
{
  map<string, double> res;
// do something with input to form the output
  return res;
}

但是,如果可以的话,请使用标准算法


返回值通常是一个不错的选择,但在 OP 的例子中,返回类型是 void 而不是 bool,因此您仍然需要有一种方法来返回其他值。 - Michael Aaron Safyan
@Michael 在 OP 的例子中,返回值的类型是 bool,我不确定我理解你的评论。你能澄清一下吗? - BЈовић
1
我想说的是,当指针/引用的返回类型为void时,使用返回值作为结果(而不是传递一个引用或指针来写入结果)是一种等效的替代方法。在这种情况下,如果返回值已经用于其他用途,则必须使用输出参数或创建一个封装对象,其中包含要返回的所有信息。 - Michael Aaron Safyan
@Michael,就像你所说的:这只是一种风格问题。而我所说的是:并非所有情况都适用同一种方法。 - BЈовић

0

你的第二个代码示例似乎有误,因为你试图接受一个指向引用的指针,这应该无法编译。

按引用传递和按指针传递具有同样的效率(如果我理解正确的话,引用本质上是自动创建的指针,然后无缝地进行了解引用),但是按引用传递更安全:

  • 你不能有一个空引用。
  • 你不能改变所引用的地址,而指针则允许你这样做。

有些人喜欢显式指针语法,以明确表明它是传递给对象的指针(因为你必须进行解引用)。


@Andrew:我曾经说过“你不能有一个空引用”,几乎因此被打死,因为(int&)(*(int*)NULL)在技术上是一个空引用。我同意你所说的话,但只是提醒你,人们可能会对这句话感到惊慌失措。 :) - user541686
@Mehrdad:这是一个错误的反驳,因为解引用空指针会导致未定义的行为。换句话说,如果我有一个函数 void f(int& i),我知道 i 总是引用一个 int。如果你说,“i 可能不是,传递 *((int*)0)”(顺便说一下,最后的强制转换到 int& 是多余的),那么你已经失败了,因为在该表达式求值之后,你不能假设任何程序行为(你不能访问 f 内部的 i,因为“调用函数”是没有意义的)。换句话说,由于 UB,术语“引用”是无意义的,不能说是空的。 - GManNickG
@GMan:我同意这是一次糟糕的异常查找尝试,但请参考此线程,这是一个针对我的例子。:\ - user541686
哈哈,我想我会在发出另外二十条评论之前停下来...现在已经过了我的睡觉时间。xD 谢谢你的讨论! - user541686
1
是的,这是一个不幸的权衡;在调用时明确指定并确保它不会为NULL。如果有一种明确的引用调用语法可以提供这样的保证,那就太好了。另一方面,引用会改变解除引用的位置。检查输入是否非NULL可以使错误更接近传递NULL指针的调用站点发生。 - Michael Aaron Safyan
显示剩余2条评论

0

我假设第二个是只通过点而不是指向引用的指针。

两者之间的选择只是风格和品味的问题。这两行的编译结果可能是相同的。

通常对于输出参数,C++风格指南建议使用指针。


0

唯一需要使用引用的时候是在实现需要它们的标准运算符时。

然而,在此之外,指针应始终优先于引用用于任何应用程序方法。

  • 用户对象通常存储在指针中,智能或其他。当对象按需创建时,对引用的限制始终有效,这使得引用参数难以使用,因为需要进行笨拙的操作来传递值。referenceFunc((*pObj));

  • 通过引用传递是尝试实现契约范例的廉价替代品。C++不支持该功能。

  • 所有对传递的引用的(*somePtr)操作都意味着你最终会得到空指针进行调试,只是更加模糊,因为空指针看起来不像指针。

  • 更不用说维护问题了,当程序员无法通过简单的检查确定哪些函数参数可能会更改时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接