Scopes in C++


Reading time: 45 minutes | Coding time: 5 minutes

In general, scope is defined as the extent upto which something can be worked with (or a scope is a region of the program ). In programming also scope of a variable is defined as the extent of the program code within which the variable can we accessed or declared or worked with.

That means, When we declare a program element such as a class, function, or variable, its name can only be "seen" and used in certain parts of your program. The context in which a name is visible is called its scope. For example, if we declare a variable x within a function, x is only visible within that function body. It has local scope. We may have other variables by the same name in our program; as long as they are in different scopes, they do not violate the One Definition Rule and no error is raised.

For automatic non-static variables, scope also determines when they are created and destroyed in program memory.
The scope rules of a language are the rules that decide, in which part(s) of the program a particular piece of code or data item would be known and can be accessed therein.

Types of Scopes :-

There are 9 types of scopes in C++ which we will explore one by one:

  • Global scope
  • Local scope
  • Namespace scope
  • Class scope
  • Statement scope
  • Function scope
  • Function parameter scope
  • Enumeration scope
  • Template parameter scope

1. Global scope : -

A global name is one that is declared outside of any class, function or namespace. However, in C++ even these names exist with an implicit global namespace. The scope of global names extends from the point of declaration to the end of the file in which they are declared. For global names, visibility is also governed by the rules of linkage which determine whether the name is visible in other files in the program.

For Example :-

  • Global Variables can be accessed from any part of the program.
  • They are available through out the life time of a program.
  • They are declared at the top of the program outside all of the functions or blocks.
  • Declaring global variables : Global variables are usually declared outside of all of the functions and blocks, at the top of the program. They can be accessed from any portion of the program.
# include <iostream>
using namespace std;  
// global variable 
int global = 5; 
  
// global variable accessed from 
// within a function 
void display() 
{ 
   cout << global << endl; 
} 

// main function 
int main() 
{ 
   display(); 
   // changing value of global 
   // variable from main function 
   global = 10; 
   display(); 
} 

2. Local scope :-

A name declared within a function or lambda, including the parameter names, have local scope. They are often referred to as "locals". They are only visible from their point of declaration to the end of the function or lambda body. Local scope is a kind of block scope.

They can be used only by statements that are inside that function or block of code. Local variables are not known to functions outside their own.

Example -

# include <iostream>
using namespace std;
int main () {
   // Local variable declaration:
   int a, b;
   int c;
 
   // actual initialization
   a = 10;
   b = 20;
   c = a + b;
   cout &lt;&lt; c;
   return 0;
 }    

3. Namespace scope :-

A name that is declared within a namespace, outside of any class or enum definition or function block, is visible from its point of declaration to the end of namespace. A namespace may be defined in multiple blocks across different files.

Namespaces allow us to group named entities that otherwise would have global scope into narrower scopes, giving them namespace scope. This allows organizing the elements of programs into different logical scopes referred to by names.
That means, Namespace is a container for identifiers. It puts the names of its members in a distinct space so that they don't conflict with the names in other namespaces or global namespace.

  • Namespace is a feature added in C++ and not present in C.
  • A namespace is a declarative region that provides a scope to the identifiers (names of the types, function, variables etc) inside it.
  • Multiple namespace blocks with the same name are allowed. All declarations within those blocks are declared in the named scope.

In other words, a namespace is a declarative region that provides a scope to the identifiers (the names of types, functions, variables, etc) inside it. Namespaces are used to organize code into logical groups and to prevent name collisions that can occur especially when your code base includes multiple libraries. All identifiers at namespace scope are visible to one another without qualification. Identifiers outside the namespace can access the members by using the fully qualified name for each identifier

Syntax :-

       namespace namespace_name 
       {
          int x, y; // code declarations where 
                   // x and y are declared in 
                   // namespace_name's scope
       }   

Rules To Remember :-

  1. Namespace declarations appear only at global scope.
  2. Namespace declarations can be nested within another namespace.
  3. Namespace declarations don’t have access specifiers. (Public or private)
  4. No need to give semicolon after the closing brace of definition of namespace.
  5. We can split the definition of namespace over several units.
  6. We can use an alias name for your namespace name, for ease of use.

Example for Alias :-

      namespace StudyTonightDotCom
      {
         void study();
         class Learn 
         {  
            // class defintion
         };
      }
      // St is now alias for StudyTonightDotCom
      namespace St = StudyTonightDotCom;    
  1. We cannot create instance of namespace. There can be unnamed namespaces too. Unnamed namespace is unique for each translation unit. They act exactly like named namespaces.
  2. A namespace definition can be continued and extended over multiple files, they are not redefined or overriden.
    For example, below is some header1.h header file, where we define a namespace:
          namespace MySpace   
          {
             int x;
             void f();
          }

We can then include the header1.h header file in some other header2.h header file and add more to an existing namespace:

         #include "header1.h";
         namespace MySpace
         {
             int y;
             void g();
         } 

Using Namespace

There are three ways to use a namespace in program :-
a. Scope resolution operator (::)
b. The using directive
c. The using declaration

a. With Scope resolution operator :-

Any name (identifier) declared in a namespace can be explicitly specified using the namespace's name and the scope resolution :: operator with the identifier.

       namespace MySpace
       {
         class A
         {
             static int i;
             public:
                 void f();
         };
    
         // class name declaration
         class B;    
         //gobal function declaration
         void func();   
       }

       // Initializing static class variable
       int MySpace::A::i=9;      

       class MySpace::B
       {
          int x;
          public:
            int getdata()
            {
               cout << x;
            }
          // Constructor declaration
          B();   
       }

       // Constructor definition
       MySpace::B::B()   
       {
          x=0;
       }    

b. With using directive:-

using keyword allows you to import an entire namespace into your program with a global scope. It can be used to import a namespace into another namespace or any program.

Conside a header file Namespace1.h:

       namespace X
       {
         int x;
         class Check
         {
            int i;
         };
       }

Including the above namespace header file in Namespace2.h file:

      #include "Namespace1.h";
      namespace Y
      {
         using namespace X;
         Check obj;
         int y;
      }

We imported the namespace X into namespace Y, hence class Check will now be available in the namespace Y.

Hence we can write the following program in a separate file, let's say program1.cpp

      #include "Namespace2.h";
      void test()
      {
         using Namespace Y;
         // creating object of class Check
         Check obj2;
       }

Hence, the using directive makes it a lot easier to use namespace, wherever we want

c. With using declaration:-

When we use using directive, we import all the names in the namespace and they are available throughout the program, that is they have a global scope.
But with using declaration, we import one specific name at a time which is available only inside the current scope.

In using declaration, we never mention the argument list of a function while importing it, hence if a namespace has overloaded function, it will lead to ambiguity.

NOTE: The name imported with using declaration can override the name imported with using directive

Consider a file Namespace.h:

        namespace X 
        {
           void f()
           {
               cout &lt;&lt;"f of X namespace\n";
           }
          void g() 
          {
               cout &lt;&lt;"g of X namespace\n";
           }
        }

      namespace Y
     {
        void f()
        {
              cout &lt;&lt;"f of Y namespace\n";
        }
        void g() 
        {
             cout &lt;&lt;"g of Y namespace\n";
        }
     }

Now let's create a new program file with name program2.cpp with below code:

      #include "Namespace.h";
      void h()
      {
         using namespace X;  // using directive
         using Y::f;         // using declaration
         f();                // calls f() of Y namespace
         X::f();             // class f() of X namespace
      }    

4. Class scope :-

Names of class members have class scope, which extends throughout the class definition regardless of the point of declaration. Class member accessibility is further controlled by the public, private, and protected keywords. Public or protected members can be accessed only by using the member-selection operators (. or ->) or pointer-to-member operators (.* or ->*).

A name declared within a member function hides a declaration of the same name whose scope extends to or past the end of the member function's class.When the scope of a declaration extends to or past the end of a class definition, the regions defined by the member definitions of that class are included in the scope of the class. Members defined lexically outside of the class are also in this scope. In addition, the scope of the declaration includes any portion of the declarator following the identifier in the member definitions.

The name of a class member has class scope and can only be used in the following cases:

  • In a member function of that class
  • In a member function of a class derived from that class
  • After the . (dot) operator applied to an instance of that class
  • After the . (dot) operator applied to an instance of a class derived from that class, as long as the derived class does not hide the name
  • After the -> (arrow) operator applied to a pointer to an instance of that class
  • After the -> (arrow) operator applied to a pointer to an instance of a class derived from that class, as long as the derived class does not hide the name
  • After the :: (scope resolution) operator applied to the name of a class
  • After the :: (scope resolution) operator applied to a class derived from that class

Example :-

      class X {
          int f(int a = n) 
          {                    // X::n is in scope inside default parameter
          return a*n;         // X::n is in scope inside function body
          }
          using r = int;
          r g();
          int i = n*2;              // X::n is in scope inside initializer
 
          //  int x[n];            // Error: n is not in scope in class body
          static const int n = 1;
          int x[n];                // OK: n is now in scope in class body
       };
 
       //r X::g() {       // Error: r is not in scope outside of out-of-class member        function body
       auto X::g()->r {            // OK: trailing return type X::r is in scope
       return n;            // X::n is in scope in out-of-class member function body
       }

5. Statement scope :-

Names declared in a for, if, while, or switch statement are visible until the end of the statement block.

6. Function scope :-

The variables declared in the outermost block of a function have function scope i.e., they can be accessed only in the function that declares them. Also labels (of goto) have function scope i.e., they cannot be used outside the function.
That means, A label has function scope, which means it is visible throughout a function body even before its point of declaration. Function scope makes it possible to write statements like goto cleanup before the cleanup label is declared.

Example

      void f()
      {
         {   
            goto label;        // label in scope even though declared later
            label:;
         }
         goto label;          // label ignores block scope
      }
 
      void g()
      {
          goto label;        // error: label not in scope in g()
      }    

7. Function parameter scope :-

The potential scope of a function parameter (including parameters of a lambda expression) or of a function-local predefined variable begins at its point of declaration.

  • If the nearest enclosing function declarator is not the declarator of a function definition, its potential scope ends at the end of that function declarator.
  • Otherwise, its potential scope ends at the end of the last exception handler of the function-try-block, or at the end of the function body if a function try block was not used.

Example :-

      const int n = 3;

      int f1(int n,                 // scope of global 'n' interrupted,
                                   // scope of the parameter 'n' begins
             int y = n);          // error: default argument references a parameter

      int (*(*f2)(int n))[n];       // OK: the scope of the function parameter 'n'
                                   // ends at the end of its function declarator
                                  // in the array declarator, global n is in scope
     //(this declares a pointer to function returning a pointer to an array of 3 int

      // by contrast
      auto (*f3)(int n)->int (*)[n];         // error: parameter 'n' as array bound
      int f(int n = 2)                      // scope of 'n' begins
      try                                  // function try block
      {                                   // the body of the function begins
         ++n;               // 'n' is in scope and refers to the function parameter
         {
           int n = 2;                // scope of the local variable 'n' begins
                                    // scope of function parameter 'n' interrupted 
           ++n;                    // 'n' refers to the local variable in this block
         }                        // scope of the local variable 'n' ends
                                 // scope of function parameter 'n' resumes
      } catch(...) {
         ++n;                  // n is in scope and refers to the function parameter
           throw;
        }       // last exception handler ends, scope of function parameter 'n' ends
    
        int a = n;            // OK: global 'n' is in scope

8.Enumeration scope :-

An enumeration provides context to describe a range of values which are represented as named constants and are also called enumerators. In the original C and C++ enum types, the unqualified enumerators are visible throughout the scope in which the enum is declared. In scoped enums, the enumerator name must be qualified by the enum type name.

The following example demonstrates this basic difference between the two kinds of enums:

      namespace CardGame_Scoped
      {
         enum class Suit { Diamonds, Hearts, Clubs, Spades };
         void PlayCard(Suit suit)
         {
            if (suit == Suit::Clubs) // Enumerator must be qualified by enum type
                { /*...*/}
            }
        }

      namespace CardGame_NonScoped
      {
         enum Suit { Diamonds, Hearts, Clubs, Spades };

         void PlayCard(Suit suit)
         {
           if (suit == Clubs) // Enumerator is visible without qualification
           { /*...*/
                }
         }
      }

Every name in an enumeration is assigned an integral value that corresponds to its place in the order of the values in the enumeration. By default, the first value is assigned 0, the next one is assigned 1, and so on, but you can explicitly set the value of an enumerator, as shown here:

         enum Suit { Diamonds = 1, Hearts, Clubs, Spades };

9. Template parameter scope :-

The potential scope of a template parameter name begins immediately at the point of declaration and continues to the end of the smallest template declaration in which it was introduced. In particular, a template parameter can be used in the declarations of subsequent template parameters and in the specifications of base classes, but can't be used in the declarations of the preceding template parameters.

        template< typename T,             // scope of T begins
                  T* p,                  // T can be used for a non-type parameter
                  class U = T           // T can be used for a default type
                >
        class X : public Array<T>      // T can be used in base class name
        {
           // T can be used inside the body as well
        };                            // scopes of T and U end, scope of X continues

The potential scope of the name of the parameter of a template template parameter is the smallest template parameter list in which that name appears

        template< template<                     // template template parameter
                            typename Y,        // scope of Y begins
                            typename G = Y    // Y is in scope
                          >                  // scopes of Y and G end
                  class T,
        //          typename U = Y          // Error: Y is not in scope
                  typename U
                >
        class X
        {
        }; // scopes of T and U ends