Każdy blok definiuje swój własny obszar zasięgu. Co więc się stanie, gdy w zagnieżdżonym bloku mamy zmienną, która ma taką samą nazwę jak zmienna w bloku zewnętrznym? Kiedy tak się dzieje, zagnieżdżona zmienna „ukrywa” zmienną zewnętrzną w obszarach, w których obydwie wchodzą w zakres. To się nazywa ukrywanie imienia lub cieniowanie.
Zacienianie zmiennych lokalnych
#include <iostream>
int main()
{ // outer block
int apples { 5 }; // here's the outer block apples
{ // nested block
// apples refers to outer block apples here
std::cout << apples << '\n'; // print value of outer block apples
int apples{ 0 }; // define apples in the scope of the nested block
// apples now refers to the nested block apples
// the outer block apples is temporarily hidden
apples = 10; // this assigns value 10 to nested block apples, not outer block apples
std::cout << apples << '\n'; // print value of nested block apples
} // nested block apples destroyed
std::cout << apples << '\n'; // prints value of outer block apples
return 0;
} // outer block apples destroyedJeśli uruchomisz ten program, wyświetli się:
5 10 5
W powyższym programie najpierw deklarujemy zmienną o nazwie apples w bloku zewnętrznym. Zmienna ta jest widoczna w obrębie wewnętrznego bloku, co możemy zobaczyć drukując jej wartość (5). Następnie deklarujemy inną zmienną (również o nazwie apples) w zagnieżdżonym bloku. Od tego miejsca do końca bloku nazwa apples odnosi się do zagnieżdżonego bloku apples, a nie zewnętrzny blok apples.
Zatem, kiedy przypisujemy wartość 10 Do apples, przypisujemy go do zagnieżdżonego bloku apples. Po wydrukowaniu tej wartości (10), zagnieżdżony blok kończy się i zagnieżdżony blok apples jest zniszczony. Istnienie i wartość bloku zewnętrznego apples nie ma to wpływu i udowadniamy to, drukując wartość zewnętrznego bloku apples (5).
Należy pamiętać, że jeśli zagnieżdżony blok apples nie została zdefiniowana, nazwa apples w zagnieżdżonym bloku nadal będzie odnosić się do bloku zewnętrznego apples, a więc przypisanie wartości 10 Do apples dotyczyłoby bloku zewnętrznego apples:
#include <iostream>
int main()
{ // outer block
int apples{5}; // here's the outer block apples
{ // nested block
// apples refers to outer block apples here
std::cout << apples << '\n'; // print value of outer block apples
// no inner block apples defined in this example
apples = 10; // this applies to outer block apples
std::cout << apples << '\n'; // print value of outer block apples
} // outer block apples retains its value even after we leave the nested block
std::cout << apples << '\n'; // prints value of outer block apples
return 0;
} // outer block apples destroyedPowyższy program wypisuje:
5 10 10
Wewnątrz zagnieżdżonego bloku nie ma możliwości bezpośredniego dostępu do cieniowanej zmiennej z bloku zewnętrznego.
Zacienianie zmiennych globalnych
Podobnie jak zmienne w bloku zagnieżdżonym mogą przesłaniać zmienne w bloku zewnętrznym, zmienne lokalne o tej samej nazwie co zmienna globalna będą przesłaniać zmienną globalną wszędzie tam, gdzie zmienna lokalna znajduje się w zasięgu:
#include <iostream>
int value { 5 }; // global variable
void foo()
{
std::cout << "global variable value: " << value << '\n'; // value is not shadowed here, so this refers to the global value
}
int main()
{
int value { 7 }; // hides the global variable value (wherever local variable value is in scope)
++value; // increments local value, not global value
std::cout << "local variable value: " << value << '\n';
foo();
return 0;
} // local value is destroyedTen kod wypisuje:
local variable value: 8 global variable value: 5
Ponieważ jednak zmienne globalne są częścią globalnej przestrzeni nazw, możemy użyć operatora zasięgu (::) bez przedrostka, aby poinformować kompilator, że mamy na myśli zmienną globalną, a nie zmienną lokalną.
#include <iostream>
int value { 5 }; // global variable
int main()
{
int value { 7 }; // hides the global variable value
++value; // increments local value, not global value
--(::value); // decrements global value, not local value (parenthesis added for readability)
std::cout << "local variable value: " << value << '\n';
std::cout << "global variable value: " << ::value << '\n';
return 0;
} // local value is destroyedTen kod wypisuje:
local variable value: 8 global variable value: 4
Unikaj zmiennego cieniowania
Ogólnie rzecz biorąc, należy unikać przesłaniania zmiennych lokalnych, ponieważ może to prowadzić do niezamierzonych błędów w przypadku użycia lub modyfikacji niewłaściwej zmiennej. Niektóre kompilatory wyświetlają ostrzeżenie, gdy zmienna jest cieniowana.
Z tego samego powodu, dla którego zalecamy unikanie cieniowania zmiennych lokalnych, zalecamy również unikanie cieniowania zmiennych globalnych. Można tego w prosty sposób uniknąć, jeśli wszystkie nazwy globalne używają przedrostka „g_”.
Najlepsza praktyka
Unikaj zmiennego cieniowania.
Dla użytkowników gcc
GCC i Clang wspierają flagę -Wshadow który wygeneruje ostrzeżenia, jeśli zmienna jest cieniowana. Istnieje kilka podwariantów tej flagi (-Wshadow=global, -Wshadow=local, I -Wshadow=compatible-local. Skonsultuj się z Dokumentacja GCC w celu wyjaśnienia różnic.
W programie Visual Studio takie ostrzeżenia są domyślnie włączone.

