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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/334856/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:43:58  来源:igfitidea点击:

Are there benefits of passing by pointer over passing by reference in C++?

c++pointersparameter-passingpass-by-reference

提问by Matt Pascoe

What are the benefits of passing by pointer over passing by reference in C++?

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

Lately, I have seen a number of examples that chose passing function arguments by pointers instead of passing by reference. Are there benefits to doing this?

最近,我看到了许多选择通过指针传递函数参数而不是通过引用传递的示例。这样做有好处吗?

Example:

例子:

func(SPRITE *x);

with a call of

用一个电话

func(&mySprite);

vs.

对比

func(SPRITE &x);

with a call of

用一个电话

func(mySprite);

采纳答案by Adam Rosenfield

A pointer can receive a NULL parameter, a reference parameter can not. If there's ever a chance that you could want to pass "no object", then use a pointer instead of a reference.

指针可以接收 NULL 参数,引用参数不能。如果您有可能想要传递“无对象”,请使用指针而不是引用。

Also, passing by pointer allows you to explicitly see at the call site whether the object is passed by value or by reference:

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

// 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);

回答by Johannes Schaub - litb

Passing by pointer

通过指针传递

  • Caller has to take the address -> not transparent
  • A 0 value can be provided to mean nothing. This can be used to provide optional arguments.
  • 来电者必须取地址 -> 不透明
  • 可以提供 0 值来表示nothing。这可用于提供可选参数。

Pass by reference

通过引用传递

  • Caller just passes the object -> transparent. Has to be used for operator overloading, since overloading for pointer types is not possible (pointers are builtin types). So you can't do string s = &str1 + &str2;using pointers.
  • No 0 values possible -> Called function doesn't have to check for them
  • Reference to const also accepts temporaries: void f(const T& t); ... f(T(a, b, c));, pointers cannot be used like that since you cannot take the address of a temporary.
  • Last but not least, references are easier to use -> less chance for bugs.
  • 调用者只是传递对象 -> 透明。必须用于运算符重载,因为指针类型的重载是不可能的(指针是内置类型)。所以你不能 string s = &str1 + &str2;使用指针。
  • 不可能有 0 个值 -> 调用函数不必检查它们
  • 对 const 的引用也接受临时变量: void f(const T& t); ... f(T(a, b, c));,不能像那样使用指针,因为您不能获取临时变量的地址。
  • 最后但并非最不重要的一点是,引用更易于使用 -> 出现错误的机会更少。

回答by R. Navega

I like the reasoning by an article from "cplusplus.com:"

我喜欢“cplusplus.com”上一篇文章的推理:

  1. Pass by value when the function does not want to modify the parameter and the value is easy to copy (ints, doubles, char, bool, etc... simple types. std::string, std::vector, and all other STL containers are NOT simple types.)

  2. Pass by const pointer when the value is expensive to copy AND the function does not want to modify the value pointed to AND NULL is a valid, expected value that the function handles.

  3. Pass by non-const pointer when the value is expensive to copy AND the function wants to modify the value pointed to AND NULL is a valid, expected value that the function handles.

  4. Pass by const reference when the value is expensive to copy AND the function does not want to modify the value referred to AND NULL would not be a valid value if a pointer was used instead.

  5. Pass by non-cont reference when the value is expensive to copy AND the function wants to modify the value referred to AND NULL would not be a valid value if a pointer was used instead.

  6. When writing template functions, there isn't a clear-cut answer because there are a few tradeoffs to consider that are beyond the scope of this discussion, but suffice it to say that most template functions take their parameters by value or (const) reference, however because iterator syntax is similar to that of pointers (asterisk to "dereference"), any template function that expects iterators as arguments will also by default accept pointers as well (and not check for NULL since the NULL iterator concept has a different syntax).

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

  1. 当函数不想修改参数且该值易于复制时按值传递(ints、doubles、char、bool 等...简单类型。std::string、std::vector 和所有其他 STL容器不是简单类型。)

  2. 当值复制成本高且函数不想修改指向的值且 NULL 是函数处理的有效预期值时,通过 const 指针传递。

  3. 当值复制成本高且函数想要修改指向的值时传递非常量指针,并且 NULL 是函数处理的有效的预期值。

  4. 当值复制成本高且函数不想修改引用的值时,通过 const 引用传递,如果使用指针,则 NULL 将不是有效值。

  5. 当值复制成本高且函数想要修改引用的值时,通过非连续引用传递,如果使用指针,则 NULL 将不是有效值。

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

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

What I take from this is that the major difference between choosing to use a pointer or reference parameter is if NULL is an acceptable value. That's it.

我从中得到的是,选择使用指针或引用参数之间的主要区别是 NULL 是否是可接受的值。就是这样。

Whether the value is input, output, modifiable etc. should be in the documentation / comments about the function, after all.

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

回答by Michael Burr

Allen Holub's "Enough Rope to Shoot Yourself in the Foot" lists the following 2 rules:

Allen Holub 的“足够的绳子可以用脚射击自己”列出了以下 2 条规则:

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

He lists several reasons why references were added to C++:

他列出了将引用添加到 C++ 的几个原因:

  • they are necessary to define copy constructors
  • they are necessary for operator overloads
  • constreferences allow you to have pass-by-value semantics while avoiding a copy
  • 它们是定义复制构造函数所必需的
  • 它们是运算符重载所必需的
  • const引用允许您在避免复制的同时具有按值传递的语义

His main point is that references should not be used as 'output' parameters because at the call site there's no indication of whether the parameter is a reference or a value parameter. So his rule is to only use constreferences as arguments.

他的主要观点是引用不应该用作“输出”参数,因为在调用站点没有指示参数是引用还是值参数。所以他的规则是只使用const引用作为参数。

Personally, I think this is a good rule of thumb as it makes it more clear when a parameter is an output parameter or not. However, while I personally agree with this in general, I do allow myself to be swayed by the opinions of others on my team if they argue for output parameters as references (some developers like them immensely).

就个人而言,我认为这是一个很好的经验法则,因为它使参数何时是输出参数变得更加清楚。然而,虽然我个人总体上同意这一点,但我确实允许自己被团队中其他人的意见所左右,如果他们认为输出参数作为参考(一些开发人员非常喜欢它们)。

回答by Mr.Ree

Clarifications to the preceding posts:

对之前帖子的澄清:



References are NOTa guarantee of getting a non-null pointer. (Though we often treat them as such.)

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

While horrifically bad code, as in take you out behind the woodshed badcode, the following will compile & run: (At least under my compiler.)

虽然可怕的糟糕代码,就像带你到木棚代码后面一样,以下将编译和运行:(至少在我的编译器下。)

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

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};


The real issue I have with references lies with other programmers, henceforth termed IDIOTS, who allocate in the constructor, deallocate in the destructor, and fail to supply a copy constructor or operator=().

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

Suddenly there's a world of difference between foo(BAR bar)and foo(BAR &bar). (Automatic bitwise copy operation gets invoked. Deallocation in destructor gets invoked twice.)

突然之间foo(BAR bar)foo(BAR &bar)之间有了天壤之别。(自动按位复制操作被调用。析构函数中的释放被调用两次。)

Thankfully modern compilers will pick up this double-deallocation of the same pointer. 15 years ago, they didn't. (Under gcc/g++, use setenv MALLOC_CHECK_ 0to revisit the old ways.) Resulting, under DEC UNIX, in the same memory being allocated to two different objects. Lots of debugging fun there...

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



More practically:

更实际:

  • References hide that you are changing data stored someplace else.
  • It's easy to confuse a Reference with a Copied object.
  • Pointers make it obvious!
  • 引用隐藏了您正在更改存储在其他地方的数据。
  • 很容易将引用与复制对象混淆。
  • 指针一目了然!

回答by Tom VH

Most of the answers here fail to address the inherent ambiguity in having a raw pointer in a function signature, in terms of expressing intent. The problems are the following:

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

  • The caller does not know whether the pointer points to a single objects, or to the start of an "array" of objects.

  • The caller does not know whether the pointer "owns" the memory it points to. IE, whether or not the function should free up the memory. (foo(new int)- Is this a memory leak?).

  • The caller does not know whether or not nullptrcan be safely passed into the function.

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

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

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

All of these problems are solved by references:

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

  • References always refer to a single object.

  • References never own the memory they refer to, they are merely a view into memory.

  • References can't be null.

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

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

  • 引用不能为空。

This makes references a much better candidate for general use. However, references aren't perfect - there are a couple of major problems to consider.

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

  • No explicit indirection. This is not a problem with a raw pointer, as we have to use the &operator to show that we are indeed passing a pointer. For example, int a = 5; foo(a);It is not clear at all here that a is being passed by reference and could be modified.
  • Nullability. This weakness of pointers can also be a strength, when we actually wantour references to be nullable. Seeing as std::optional<T&>isn't valid (for good reasons), pointers give us that nullability you want.
  • 没有明确的间接。这不是原始指针的问题,因为我们必须使用&运算符来表明我们确实在传递一个指针。例如,int a = 5; foo(a);这里根本不清楚 a 是通过引用传递的并且可以被修改。
  • 可空性。当我们实际上希望我们的引用可以为空时,指针的这种弱点也可以成为一种优势。看到 asstd::optional<T&>无效(有充分的理由),指针为我们提供了您想要的可空性。

So it seems that when we want a nullable reference with explicit indirection, we should reach for a T*right? Wrong!

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

Abstractions

抽象

In our desperation for nullability, we may reach for T*, and simply ignore all of the shortcomings and semantic ambiguity listed earlier. Instead, we should reach for what C++ does best: an abstraction. If we simply write a class that wraps around a pointer, we gain the expressiveness, as well as the nullability and explicit indirection.

在我们对可空性的绝望中,我们可能会触及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;
};

This is the most simple interface I could come up with, but it does the job effectively. It allows for initializing the reference, checking whether a value exists and accessing the value. We can use it like so:

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

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

We have acheived our goals! Let's now see the benefits, in comparison to the raw pointer.

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

  • The interface shows clearly that the reference should only refer to one object.
  • Clearly it does not own the memory it refers to, as it has no user defined destructor and no method to delete the memory.
  • The caller knows nullptrcan be passed in, since the function author explicitly is asking for an optional_ref
  • 界面清楚地表明引用应该只引用一个对象。
  • 显然它不拥有它所指的内存,因为它没有用户定义的析构函数,也没有删除内存的方法。
  • 调用者知道nullptr可以传入,因为函数作者明确要求optional_ref

We could make the interface more complex from here, such as adding equality operators, a monadic get_orand mapinterface, a method that gets the value or throws an exception, constexprsupport. That can be done by you.

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

In conclusion, instead of using raw pointers, reason about what those pointers actually mean in your code, and either leverage a standard library abstraction or write your own. This will improve your code significantly.

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

回答by Brian

Not really. Internally, passing by reference is performed by essentially passing the address of the referenced object. So, there really aren't any efficiency gains to be had by passing a pointer.

并不真地。在内部,通过引用传递本质上是通过传递被引用对象的地址来执行的。因此,通过传递指针确实没有任何效率提升。

Passing by reference does have one benefit, however. You are guaranteed to have an instance of whatever object/type that is being passed in. If you pass in a pointer, then you run the risk of receiving a NULL pointer. By using pass-by-reference, you are pushing an implicit NULL-check up one level to the caller of your function.

然而,通过引用传递确实有一个好处。保证你有一个被传入的任何对象/类型的实例。如果你传入一个指针,那么你就有收到一个 NULL 指针的风险。通过使用传递引用,您将隐式 NULL 检查推高一层给函数的调用者。