Scope

Learn more about how and when we can access variables, by understanding the importance of the scope in which they exist.

Ryan McCombe
Updated

In programming, we can imagine our code being split into regions, sometimes referred to as scopes.

When we declare a variable in one region or scope, it may not necessarily be accessible to other scopes.

Global / File Scope

We have thus far been declaring our variables at the top of our files, outside of any function body. This scope is sometimes referred to as the global scope, also known as the file scope.

This is because, as we've seen, we've been able to freely access these variables anywhere within our file:

#include <iostream>
using namespace std;

int x{42};

void Log(){ cout << "x is " << x; }

int main(){
  Log();
  cout << "\nit's definitely " << x;
}
x is 42
it's definitely 42

But this is not the only place we can declare variables. For example, we can also create variables within the scope of a function, by placing it between the { and } of the function's body.

Below, we've moved our x variable into the scope of our Log function. As a result, it is no longer in the global scope, and is therefore no longer accessible by our main function.

As such, our code throws a compilation error when main attempts to find x:

#include <iostream>
using namespace std;

void Log(){
  int x{42};
  cout << "x is " << x;
}

int main(){
  Log();
  cout << "\nit's definitely " << x;
}
error: 'x': undeclared identifier

Block Scope

Block scopes are created when we have a pair of curly braces {} in our code. For example, function bodies create a block scope, as we've seen above.

This concept isn't limited to function bodies - for example, if and else statements can also create a block scope. Let's see an example:

#include <iostream>
using namespace std;

int main() {
  bool shouldLog{true};
  if (shouldLog) {
    string Message{"Hello World"};
  }
  cout << Message;
}
error: `Message`: undeclared identifier

In this code, the variable Message is declared within the if statement's block. Consequently, Message only exists within this block and is inaccessible outside of it.

When we try to use Message outside of its block (here, in the cout statement), the compiler throws an error. This is because Message is not recognized outside its defining block.

Block scopes do not need to be associated with functions or if statements - we can open a set of curly braces any time we want, thereby creating a new scope.

It's somewhat uncommon to do this, but it highlights the key point:

#include <iostream>
using namespace std;

int main() {
  {
    string Message{"Hello World"};
  }
  cout << Message;
}
error: `Message`: undeclared identifier

Scope Access Rules

In C++, the scope access rules govern how different parts of a program can access variables.

The key rule is: a scope can access variables from its parent scopes, but not from its child scopes. The previous errors were all caused by code in a scope trying to access a variable in a nested, or child scope.

Previously, we've been writing code in function scopes that access variables in parent scopes (typically, the global scope). As we've seen, that has been successful:

#include <iostream>
using namespace std;
string Message{"Hello World"};

int main(){
  // Accessing variable in parent scope
  cout << Message; 
}
Hello World

Below, we show another variation of a child scope successfully accessing a variable in a parent scope:

#include <iostream>
using namespace std;

int main(){
  bool shouldLog{true};
  string Message{"Hello World"};
  if (shouldLog) {
    // Accessing variable in parent scope
    cout << Message;
  }
}
Hello World

Here, Message is declared in the main function's scope. The if statement within main represents a child scope, nested within the main function's scope.

As such, variables declared in main (parent scope) can be accessed in the if statement (child scope).

Test your Knowledge

Scope Access

What will the following function return?

int GetInt() {
  int x{1};
  { x++; }
  return x;
}

What will the following function return?

int GetInt() {
  {
    int x{1};
    x++;
  }
  return x;
}

Shadowed Variables / Name Hiding

Shadowing or name hiding occurs when multiple variables with the same name are accessible at once. This is the result of a variable in an inner scope using a name of a variable in one of its parent scopes.

The variable in the inner scope "shadows" the variable in the outer scope, making the outer variable inaccessible within the inner scope. Here's an example to demonstrate this concept:

#include <iostream>
using namespace std;

int main(){
  int x{1};
  {
    // This 'x' shadows the outer 'x'
    int x{2};
    cout << "Inner x: " << x; // Outputs 2
  }
  cout << "\nOuter x: " << x; // Outputs 1
}
Inner x: 2
Outer x: 1

In this example, there are two variables named x. The x inside the block is separate and shadows the x declared in the main function.

Within the block, any reference to x refers to the inner x (with value 2), not the outer x (with value 1). When the block ends, the inner x goes out of scope, and the outer x becomes accessible again.

Shadowing is typically something we want to avoid, as it can lead to confusion and bugs. However, it's still important to understand what's going on here, so we can ensure we're accessing the correct variables.

Test your Knowledge

Variable Shadowing

What will the following function return?

int GetInt() {
  int x{1};
  {
    int x{2};
  }
  return x;
}

Summary

In this lesson on scope in C++, you've learned several key concepts:

  • Global/File Scope: Variables declared outside any function have global scope and can be accessed anywhere within the file.
  • Block Scope: Variables declared within a set of {} are only accessible within those braces.
  • Scope Access Rules: A scope can access variables from its parent scopes but not from its child scopes.
  • Shadowed Variables/Name Hiding: When two variables with the same name exist in different scopes, the one in the inner scope shadows the one in the outer scope.
Next Lesson
Lesson 16 of 60

Forward Declarations

Understand what function prototypes are, and learn how we can use them to let us order our code any way we want.

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy