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

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

This question is related to c++ pointers parameter-passing pass-by-reference

The answer is


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.


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.

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.


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.

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.


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.

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!

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


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!

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.


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.

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.


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!

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.


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


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!

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.


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


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.


Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to pointers

Method Call Chaining; returning a pointer vs a reference? lvalue required as left operand of assignment error when using C++ Error: stray '\240' in program Reference to non-static member function must be called How to convert const char* to char* in C? Why should I use a pointer rather than the object itself? Function stoi not declared C pointers and arrays: [Warning] assignment makes pointer from integer without a cast Constant pointer vs Pointer to constant How to get the real and total length of char * (char array)?

Examples related to parameter-passing

How to pass parameter to a promise function Check number of arguments passed to a Bash script How to pass event as argument to an inline event handler in JavaScript? Passing Parameters JavaFX FXML Invoke a second script with arguments from a script How can I pass a member function where a free function is expected? Passing variables, creating instances, self, The mechanics and usage of classes: need explanation In Javascript/jQuery what does (e) mean? How to write a bash script that takes optional input arguments? Passing Objects By Reference or Value in C#

Examples related to pass-by-reference

Passing an integer by reference in Python Does JavaScript pass by reference? Object passed as parameter to another class, by value or reference? change values in array when doing foreach Call-time pass-by-reference has been removed C++ pass an array by reference Passing Objects By Reference or Value in C# Pass variables by reference in JavaScript JavaScript by reference vs. by value How to do the equivalent of pass by reference for primitives in Java