HOWTO · C++
Functionality and Difference Between *& and **& in C++
Understand `int*&` and `int**&` parameter declarations in C++, and compare what changes when functions receive pointers by value or reference.
On this page
In C++, *& and **& are useful symbols to recognize inside a declaration. They are not standalone operators, and neither declaration means a pointer to a reference. In a function parameter, int*& means a reference to a pointer to int; int**& means a reference to a pointer to a pointer to int.
The distinction matters when a function changes a pointed-to value, changes which object a pointer selects, or changes which pointer a pointer-to-pointer selects. The examples below compare those effects with initialized objects. If you need a pointer refresher first, see Microsoft’s C++ pointers guide.
Read *& and **& as C++ Declarators
Read the declaration by starting with the parameter name and following its declarator outward. For int*& ptr, ptr is a reference (&) to a pointer (*) whose pointed-to type is int. A reference parameter aliases the caller’s pointer; it does not create a pointer to a reference. C++ references must be initialized when created, so a reference cannot be reseated later.
In int**& ptr, the parameter is a reference to a pointer-to-pointer. The expression *ptr refers to an int*, and **ptr refers to the int reached through both pointer levels. These are declaration forms. Within an expression, by contrast, *ptr is dereferencing an existing pointer.
Pass a Pointer by Value
A parameter declared int* ptr receives a copy of the caller’s pointer. The copy and the caller’s pointer can initially select the same integer, so assigning through *ptr changes that shared integer. Reassigning ptr changes only the local copy. The caller’s pointer continues to select its original object after the function returns.
This distinction is useful when a function should update an object but should not choose a different object for the caller’s pointer. A pointer passed by value can still be null, so code must ensure it points to a valid object before dereferencing it. The example uses valid initialized objects throughout.
Pass a Pointer by Reference with int*&
Declaring the parameter as int*& ptr makes ptr an alias for the caller’s pointer variable. Assigning to *ptr changes the currently selected integer, just as it does with a pointer passed by value. Assigning a different valid address to ptr also updates the caller’s pointer itself.
The function can therefore change two things: the value of the object currently selected and which object the caller’s pointer selects. The order matters. After reassigning ptr, a later *ptr expression accesses the new target. Use this form when changing a caller-owned int* is part of the function’s intended result.
Pass a Pointer-to-Pointer by Reference with int**&
An int**& parameter aliases a caller’s pointer-to-pointer variable. Dereferencing twice with **ptr accesses the integer at the end of the chain. Assigning to *ptr changes the intermediate int*; assigning to ptr changes the caller’s int** variable itself.
The complete example passes a replacement int** that points to a second int*. The function first changes the integer reached by the original chain, then redirects the caller’s pointer-to-pointer to the replacement chain, and finally changes the integer reached by the new chain. Each level has to refer to a live object of the matching type before it is dereferenced.
Compare What Changes in Each Function
With an int* parameter, the function can modify the pointed-to integer, but reassignment of its local pointer copy does not update the caller’s pointer. With int*&, the function can modify the integer and reassign the caller’s int*. With int**&, it can modify the final integer, change the intermediate pointer, and reassign the caller’s int** variable.
The number of asterisks describes pointer levels, while & in these parameter declarations makes the parameter a reference to the pointer structure that precedes it. This is why these forms have different caller-visible effects. They do not make invalid pointers safe: each dereference still requires a valid pointer at that level. Choose the least indirection that expresses the function’s actual responsibility, and document when it changes a caller-owned pointer.
Compile and Run the Complete Example
This standalone program uses two initialized integer objects, first and second, and prints their values and pointer selections rather than machine-specific addresses. Each function demonstrates a different parameter type. The supplied run of this exact example completed successfully with exit code 0 and printed:
Run this example:
#include <iostream>
void byValue(int* ptr, int* replacement) {
*ptr = 11;
ptr = replacement;
*ptr = 21;
}
void byReference(int*& ptr, int* replacement) {
*ptr = 31;
ptr = replacement;
*ptr = 41;
}
void byPointerToPointerReference(int**& ptr, int** replacement) {
**ptr = 51;
ptr = replacement;
**ptr = 61;
}
int main() {
int first = 10;
int second = 20;
int* selected = &first;
byValue(selected, &second);
std::cout << "by value: first=" << first << " second=" << second
<< " selected=" << (selected == &first ? "first" : "second") << '\n';
first = 10;
second = 20;
selected = &first;
byReference(selected, &second);
std::cout << "by reference: first=" << first << " second=" << second
<< " selected=" << (selected == &first ? "first" : "second") << '\n';
first = 10;
second = 20;
int* firstPointer = &first;
int* secondPointer = &second;
int** selectedPointer = &firstPointer;
byPointerToPointerReference(selectedPointer, &secondPointer);
std::cout << "pointer-to-pointer reference: first=" << first
<< " second=" << second << " selected="
<< (*selectedPointer == &first ? "first" : "second") << '\n';
}
by value: first=11 second=21 selected=first
by reference: first=31 second=41 selected=second
pointer-to-pointer reference: first=51 second=61 selected=second