280

What are the benefits of passing by pointer over passing by reference in 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);
Sobi
  • 117
  • 6
Matt Pascoe
  • 8,651
  • 17
  • 42
  • 48

7 Answers7

271

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.

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.
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 11
    Passing by pointer also raises the 'Is ownership transferred or not?' question. This is not the case with references. – Frerich Raabe Oct 30 '09 at 17:53
  • 1
    You should probably add that it is cleaner to use pointer when there is ownership transfer, or for registering callbacks (item that must survive the current stack frame). – Sylvain Defresne Mar 16 '11 at 13:56
  • 55
    I disagree with "less chance for bugs". When inspecting the call site and the reader sees "foo( &s )" it is immediately clear that s may be modified. When you read "foo( s )" it is not at all clear if s may be modified. This is a major source of bugs. Perhaps there is less chance of a certain class of bugs, but overall, passing by reference is a huge source of bugs. – William Pursell Nov 21 '11 at 14:10
  • 32
    What do you mean by "transparent" ? – Gbert90 May 14 '12 at 04:35
  • 5
    @Gbert90, if you see foo(&a) at a call site, you know foo() takes a pointer type. If you see foo(a), you don't know whether it takes a reference. – Michael J. Davenport Sep 14 '12 at 16:17
  • 2
    @MichaelJ.Davenport -- that doesn't seem to be the context in which "transparent" is used in Johannes' post. – Happy Green Kid Naps Nov 14 '12 at 16:19
  • @HappyGreenKidNaps - For my benefit and that of Gbert90, could you elaborate? – Michael J. Davenport Nov 16 '12 at 18:31
  • 4
    @MichaelJ.Davenport -- in your explanation, you suggest "transparent" to mean something along the lines of "obvious that caller is passing a pointer, but not obvious that caller is passing a reference". In Johannes' post, he says "Passing by pointer -- Caller has to take the address -> not transparent" and "Pass by reference -- Caller just passes the object -> transparent" -- which is nearly opposite of what you say. I think Gbert90's question "What do you mean by "transparent"" is still valid. – Happy Green Kid Naps Nov 16 '12 at 19:55
  • 1
    Transparency means "easy for others to see what actions are performed" (wikipedia). An address-of operator at the call site makes it more transparent to readers that this argument may be modified. I've edited the answer, let's see if the change is accepted. – Jon May 20 '13 at 00:56
  • Agreed, when I was reading Adam's comment it made sense regarding transparent then when I came to this answer it just confused me because it is backwards. Just giving an outsiders point of view. – Brandon Ling Nov 30 '13 at 20:58
  • +1, great answer. Although I will admit I was momentarily confused by your use of `->` in your answer...we are talking about pointers here after all. ;) – Madbreaks Apr 28 '14 at 23:06
  • @Jon and the others: I suppose Johannes is talking about interface transparency. It means that he interface masks the complexity of the engine below. If you pass the object directly to the function, the function could get reference or get the value, you can't know, but the interface will remain the same, and it's more simple for the end user. See https://en.wikipedia.org/wiki/Transparency_(human%E2%80%93computer_interaction) – Marco Sulla Sep 14 '16 at 12:32
  • @FrerichRaabe Address could be obtained by reference though the weird semantic is a signal to some dangerous behaviours. But in a well-defined system with `RAII` it shouldn't be a problem.. – zjyhjqs Mar 31 '22 at 15:49
253

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.

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);
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • 26
    Incomplete answer. Using pointers won't authorize uses of temporary/promoted objects, nor the use of pointed object as stack-like objects. And it will suggest that the argument can be NULL when, most of the time, a NULL value should be forbidden. Read litb's answer for a complete answer. – paercebal Dec 03 '08 at 10:12
  • The second function call used to be annotated `func2 passes by reference`. Whilst I appreciate that you meant it passes "by reference" from a high-level perspective, implemented by passing a pointer at a code-level perspective, this was very confusing (see http://stackoverflow.com/questions/13382356/there-is-a-typo-in-the-answer-of-another-so-answer-or-am-i-wrong). – Lightness Races in Orbit Nov 14 '12 at 16:03
  • I just don't buy this. Yes, you pass in a pointer, so therefore it must be an output parameter, because what's pointed to can't be const? – deworde Dec 12 '12 at 10:06
  • Don't we have this passing reference in C? I am using codeblock (mingw) latest version and in that selecting a C project. Still passing by reference (func (int& a)) works. Or is it available in C99 or C11 onwards? – Jon Wheelock Oct 12 '15 at 01:18
  • 4
    @JonWheelock: No, C does not have pass-by-reference at all. `func(int& a)` is not valid C in any version of the standard. You're probably compiling your files as C++ by accident. – Adam Rosenfield Oct 12 '15 at 13:43
  • 1
    You are right. My file name was .cpp and after changing to .c it gave error. – Jon Wheelock Oct 12 '15 at 14:58
  • Don't you mean "null argument?" The parameter is in the function declaration. – ihodonald May 30 '17 at 05:56
  • 1
    A reference parameter *can* receive NULL, @AdamRosenfield. Pass it as `func(*NULL)`. Then inside the function, test with `if (&x == NULL)`. I suppose this looks ugly, but the difference between pointer and reference parameters is syntactic sugar. – Ken Jackson Feb 06 '19 at 21:49
  • @KenJackson: Nope, that's Undefined Behavior to dereference a null pointer – Adam Rosenfield Feb 25 '19 at 04:34
  • @AdamRosenfield: It's undefined behavior to perform an lvalue-to-rvalue conversion on an unseated reference (whether that reference was formed from a null pointer, invalid pointer value, or was created valid but became dangling when the referent died). The act of forming an unseated reference is not nearly so straightforward. – Ben Voigt Aug 11 '22 at 17:01
  • 1
    I am stunned every time reading C++ questions' comment sections; I am at least 90% sure that comment sections for C++ questions literally are the most heated place for debaters: very smart/sharp comment to the point with at most 1% of EQ involved. "Incomplete answer" is surely right but I just feel there's better way of saying it. – stucash Mar 03 '23 at 15:58
  • One thing never discussed here is that compile time and even runtime polymorphism is not possible with pass by reference but only pass by pointer. – Drizzle Jul 31 '23 at 07:05
106

I like the reasoning by an article from "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/

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.

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

Ralfonso
  • 1,614
  • 15
  • 30
R. Navega
  • 1,225
  • 1
  • 9
  • 9
70

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

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++:

  • they are necessary to define copy constructors
  • they are necessary for operator overloads
  • const references allow you to have pass-by-value semantics while avoiding a copy

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 const references as arguments.

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).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 10
    My stance in that argument is that if the function name makes it totally obvious, without checking the docs, that the param will be modified, then a non-const reference is OK. So personally I'd allow "getDetails(DetailStruct &result)". A pointer there raises the ugly possibility of a NULL input. – Steve Jessop Dec 02 '08 at 19:06
  • 3
    This is misleading. Even if some do not like references, they are a important part of the language and should be used as that. This line of reasoning is like saying don't use templates you can always use containers of void* to store any type. Read answer by litb. – David Rodríguez - dribeas Dec 02 '08 at 20:28
  • 5
    I don't see how this is misleading - there are times when references are required, and there are times when best practices might suggest not using them even if you could. The same can be said for any feature of the language - inheritance, non-member friends, operator overloading, MI, etc... – Michael Burr Dec 02 '08 at 21:34
  • By the way, I agree that litb's answer is very good, and is certainly more comprehensive than this one - I just elected to focus on discussing a rationale for avoiding using references as output parameters. – Michael Burr Dec 02 '08 at 21:38
  • 1
    This rule is used in google c++ style guide: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Reference_Arguments – Anton Daneyko Nov 30 '10 at 21:37
  • I think there is a very important exception of those rules: the `swap` function plays a really important role in the copy-swap idiom, but without declaring its parameters as non-const references, there is no way to do what it intended to do. Using pointers here will be troublesome and breaks its exception-free promise easily. – Earth Engine Nov 08 '16 at 02:22
  • Qt is using this rule in its functions. – Hadi Navapour Feb 16 '23 at 07:50
  • No one mentioned polymorphism – Drizzle Jul 31 '23 at 07:09
10

Clarifications to the preceding posts:


References are NOT a 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 bad code, 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=().

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.)

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_ 0 to revisit the old ways.) Resulting, under DEC UNIX, in the same memory being allocated to two different objects. Lots of debugging fun there...


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!
Mr.Ree
  • 8,320
  • 27
  • 30
  • 17
    that's not the problem of the function or references. you are breaking language rules. dereferencing a null pointer by itself is already undefined behavior. "References are NOT a guarantee of getting a non-null pointer.": the standard itself says they are. other ways constitute undefined behavior. – Johannes Schaub - litb Dec 03 '08 at 04:47
  • 1
    I agree with litb. While true, the code you are showing us is more sabotage than anything else. There are ways to sabotage anything, including both the "reference" and "pointer" notations. – paercebal Dec 03 '08 at 10:00
  • 1
    I did say it was "take you out behind the woodshed bad code"! In the same vein, you can also have i=new FOO; delete i; test(*i); Another (unfortunately common) dangling pointer/reference occurrence. – Mr.Ree Dec 03 '08 at 17:32
  • 1
    It's actually not *dereferencing* NULL that's the problem, but rather *USING* that dereferenced (null) object. As such, there really is no difference (other than syntax) between pointers and references from a language-implementation perspective. It's the users who have different expectations. – Mr.Ree Dec 03 '08 at 17:33
  • 2
    Regardless of what you do with the reference returned, the moment you say `*i`, your program has undefined behavior. For instance, the compiler can see this code and assume "OK, this code has undefined behavior in all code paths, so this entire function must be unreachable." Then it will assume that all branches that lead to this function are not taken. This is a regularly performed optimization. – David Stone Jun 28 '15 at 14:51
  • as Scott Meyers said in his book "More Effective C++" in page 10 regarding make reference refer to dereferenced null pointer "well this is eveil, pure and simple. The results are undefined and people who write this kind of code should be shunned untill they agree to cease and desist" – Arkady Godlin Apr 10 '18 at 14:55
  • No, @DavidStone, the `test(*i)` statement does *not* dereference NULL. NULL would only be dereferenced if the function accessed parameter `a` without the `&` operator. Perhaps it's assumed to be impossible to pass NULL just to avoid the need to write more bulletproof code. – Ken Jackson Feb 06 '19 at 22:12
  • @KenJackson: http://eel.is/c++draft/dcl.ref#5 "A reference shall be initialized to refer to a valid object or function. [ Note: In particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior." – David Stone Feb 07 '19 at 23:11
  • The clause of the rule you cited, @DavidStone, seems to limit the propriety of what the programmer may do, rather than specify what the compiler shall do. That's language *use*, not *implementation* so one wonders if it's in scope. Regardless, in the example given, NULL is not dereferenced. – Ken Jackson Feb 08 '19 at 13:54
9

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 nullptr can be safely passed into the function.

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 want our references to be nullable. Seeing as std::optional<T&> isn't valid (for good reasons), pointers give us that nullability you want.

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

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.

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 nullptr can be passed in, since the function author explicitly is asking for an optional_ref

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

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.

themeeman
  • 429
  • 5
  • 6
3

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.

Brian
  • 3,457
  • 4
  • 31
  • 41
  • 1
    Thats both an advantage and a disadvantage. Many APIs use NULL pointers to mean something useful (ie NULL timespec wait forever, while the value means wait that long). – Greg Rogers Dec 02 '08 at 18:17
  • sometimes you can even gain performance by using references, since they don't need to take any storage and don't have any addresses assigned for themself. no indirection required. – Johannes Schaub - litb Dec 02 '08 at 18:22
  • 1
    @Brian: I don't want to be nit-picking but: I would _not_ say one is _guaranteed_ to get an instance when getting a reference. Dangling references are still possible if the caller of a function de-references a dangling pointer, which the callee cannot know. – foraidt Dec 02 '08 at 18:18
  • Programs which contain dangling references are not valid C++. Therefore, yes, the code *can* assume that all references are valid. – Konrad Rudolph Dec 02 '08 at 18:26
  • 2
    I can definitely dereference a null pointer and the compiler won't be able to tell... if the compiler can't tell it's "invalid C++", is it really invalid? – rmeador Dec 02 '08 at 18:34
  • Yes, it's invalid even if the compiler can't tell. Merely creating a dangling reference is specified to result in undefined behaviour. You can (well, you have no choice really) write code on the assumption that the program is in a defined state. – Steve Jessop Dec 02 '08 at 19:09
  • So to be nit-picky one could say "assuming that the program state isn't already FUBARed, you're guaranteed an instance", instead of "you're guaranteed an instance". A dangling reference is an example of a FUBARed program state, but it should go without saying that your caller obeys the rules. – Steve Jessop Dec 02 '08 at 19:13