Variable shadowing happens when a variable declared in a specific scope has the same name as a variable in an outer scope.
This can lead to three main problems:
- Confusion: The same name can refer to different variables in different parts of the scope, making the code hard to read and understand.
- Unintended Behavior: You might accidentally use the wrong variable, leading to hard-to-detect bugs.
- Maintenance Issues: If the inner variable is removed or renamed, the code’s behavior might change unexpectedly because the outer variable is
now being used.
To avoid these problems, rename the shadowing, shadowed, or both variables to accurately represent their purpose with unique and meaningful
names.
The examples below show typical situations in which shadowing can occur.
- Parameter shadowing
void f(int x, bool b) {
int y = 4;
if (b) {
int x = 7; // Noncompliant: the parameter "x" is shadowed.
int y = 9; // Noncompliant: the local variable "y" is shadowed.
// ...
}
}
- Member variable shadowing
class Foo {
private:
int myField;
public:
void doSomething() {
int myField = 0; // Noncompliant: Foo::myField is shadowed.
// ...
}
};
- Global variable shadowing
namespace ns {
int state;
void bar() {
int state = 0; // Noncompliant: the namespace variable is shadowed.
}
}
Exceptions
It is common practice to have constructor arguments shadowing the fields they initialize in the member initializer list. This pattern
avoids the need to select new names for the constructor arguments and will not be reported by this rule.
class Point {
public:
Point(int x, int y)
: x(x) // Compliant by exception: the parameter "x" is used
// in the member initializer list.
{
y = y; // Noncompliant: the parameter is assigned to itself
// and the member "y" is not initialized.
}
private:
int x;
int y;
};
Caveats
Shadowing in if
, else if
, and else
Variables can be introduced in the condition of an if
statement. Their scope includes the optional else
statement, which
may be surprising. Consequently, such variables can be shadowed in an else if
statement. This can be even more confusing and result in
unintended behavior, as illustrated in this example:
using ExpectedData = std::expected<std::string, std::error_code>;
if (ExpectedData e = readData()) {
printMessage(e.value());
} else if (ExpectedData e = readFallbackSource()) { // Noncompliant
printMessage(e.value());
} else {
logError(
"Initial source failed with: ",
e.error() // Contrary to the intention, the second "e" is used.
);
}
Shadowing of inaccessible declarations
This rule also raises issues on some variables, although they do not shadow another variable according to a strict interpretation of the C++
language. There are mainly two reasons for this.
- Primarily, the readability and maintainability of the code are impaired. Readers need an advanced understanding of the C++ language to
understand the subtle differences.
- Secondly, a small change can lead to actual shadowing. This can lead to subtle bugs when updating the code.
Here is an example with nested classes:
class A {
public:
int x;
class B;
};
class A::B {
void f(int x) { // Noncompliant: The parameter "x" shadows the field "A::x".
// ...
}
};
In the above example, A::x
cannot be used from A::B
member functions because it is not a static
field. This
can lead to surprising effects when moving code around, particularly if the declaration of A::x
was changed from int x;
to
static int x;
.
You should always avoid shadowing to avoid any confusion and increase the maintainability of your code.