Variable Shadowing (Name Hiding)

Each block defines its own scope region. So what happens when we have a variable inside a nested block that has the same name as a variable in an outer block? When this occurs, the nested variable "hides" the outer variable in areas where they are both in scope. This is called name hiding or shadowing.

Shadowing of local variables

#include <iostream>

int main()
{ // outer block
    int books{10}; // here's the outer block books

    { // nested block
        // books refers to outer block books here
        std::cout << books << '\n'; // print value of outer block books

        int books{3}; // define books in the scope of the nested block

        // books now refers to the nested block books
        // the outer block books is temporarily hidden

        books = 25; // this assigns value 25 to nested block books, not outer block books

        std::cout << books << '\n'; // print value of nested block books
    } // nested block books destroyed

    std::cout << books << '\n'; // prints value of outer block books

    return 0;
} // outer block books destroyed

If you run this program, it prints:

10 25 10

In this program, we first declare a variable named books in the outer block. This variable is visible within the inner block, as demonstrated by printing its value (10). Then we declare a different variable (also named books) in the nested block. From this point until the end of the block, the name books refers to the nested block books, not the outer block books.

Thus, when we assign value 25 to books, we're assigning it to the nested block books. After printing this value (25), the nested block ends and nested block books is destroyed. The existence and value of outer block books remains unaffected, which we prove by printing the value of outer block books (10).

Note that if the nested block books had not been defined, the name books in the nested block would still refer to the outer block books, so the assignment of value 25 to books would have applied to the outer block books:

#include <iostream>

int main()
{ // outer block
    int books{10}; // here's the outer block books

    { // nested block
        // books refers to outer block books here
        std::cout << books << '\n'; // print value of outer block books

        // no inner block books defined in this example

        books = 25; // this applies to outer block books

        std::cout << books << '\n'; // print value of outer block books
    } // outer block books retains its value even after we leave the nested block

    std::cout << books << '\n'; // prints value of outer block books

    return 0;
} // outer block books destroyed

The above program prints:

10 25 25

When inside the nested block, there's no way to directly access the shadowed variable from the outer block.

Shadowing of global variables

Similar to how variables in a nested block can shadow variables in an outer block, local variables with the same name as a global variable will shadow the global variable wherever the local variable is in scope:

#include <iostream>
int total{100}; // global variable

void displayTotal()
{
    std::cout << "global variable total: " << total << '\n'; // total is not shadowed here, so this refers to the global total
}

int main()
{
    int total{50}; // hides the global variable total (wherever local variable total is in scope)

    ++total; // increments local total, not global total

    std::cout << "local variable total: " << total << '\n';

    displayTotal();

    return 0;
} // local total is destroyed

This code prints:

local variable total: 51 global variable total: 100

However, because global variables are part of the global namespace, we can use the scope operator (::) with no prefix to tell the compiler we mean the global variable instead of the local variable.

#include <iostream>
int total{100}; // global variable

int main()
{
    int total{50}; // hides the global variable total
    ++total; // increments local total, not global total

    --(::total); // decrements global total, not local total (parenthesis added for readability)

    std::cout << "local variable total: " << total << '\n';
    std::cout << "global variable total: " << ::total << '\n';

    return 0;
} // local total is destroyed

This code prints:

local variable total: 51 global variable total: 99

Avoid variable shadowing

Shadowing of local variables should generally be avoided, as it can lead to inadvertent errors where the wrong variable is used or modified. Some compilers will issue a warning when a variable is shadowed.

For the same reason that we recommend avoiding shadowing local variables, we recommend avoiding shadowing global variables as well. This is trivially avoidable if all of your global names use a "g_" prefix.

Best Practice
Avoid variable shadowing.

Advanced note: For GCC users: GCC and Clang support the flag -Wshadow that will generate warnings if a variable is shadowed. There are several subvariants of this flag (-Wshadow=global, -Wshadow=local, and -Wshadow=compatible-local). Visual Studio has such warnings enabled by default.

Summary

Name hiding (shadowing): When a variable in a nested scope has the same name as a variable in an outer scope, the inner variable hides the outer variable wherever both are in scope.

Local variable shadowing: When a variable in a nested block shadows a variable in an outer block, there's no way to directly access the shadowed outer variable from within the inner block.

Global variable shadowing: Local variables can shadow global variables with the same name. The scope resolution operator (::) with no prefix allows access to the global variable even when shadowed (e.g., ::total refers to the global total).

Why to avoid shadowing: Variable shadowing can lead to inadvertent errors where the wrong variable is used or modified. It makes code harder to understand and maintain.

Compiler warnings: Modern compilers can warn about shadowing. GCC/Clang provide -Wshadow flags, and Visual Studio enables such warnings by default.

Shadowing is a common source of subtle bugs in C++ programs. While the language allows it, best practice is to avoid giving variables in nested scopes the same names as variables in outer scopes. Using meaningful, distinct variable names throughout your code prevents these issues entirely.