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