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
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.
&
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. 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!
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.
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.
nothing
. This can be used to provide optional arguments.string s = &str1 + &str2;
using pointers. 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.I like the reasoning by an article from "cplusplus.com:"
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.)
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.
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.
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.
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.
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).
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.
nothing
. This can be used to provide optional arguments.string s = &str1 + &str2;
using pointers. 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.I like the reasoning by an article from "cplusplus.com:"
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.)
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.
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.
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.
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.
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).
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.
nothing
. This can be used to provide optional arguments.string s = &str1 + &str2;
using pointers. 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.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:
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++:
const
references allow you to have pass-by-value semantics while avoiding a copyHis 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:
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.
nothing
. This can be used to provide optional arguments.string s = &str1 + &str2;
using pointers. 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.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.
&
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. 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!
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.
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:
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++:
const
references allow you to have pass-by-value semantics while avoiding a copyHis 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:
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++:
const
references allow you to have pass-by-value semantics while avoiding a copyHis 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.
Source: Stackoverflow.com