20.2.1.  Default Scope of Identifiers - More Detail

[ fromfile: scopestorage.xml id: defaultscope ]

We examine the six principal scopes and provide some examples.

  1. Block Scope. 

    An identifier declared inside curly braces { ... } (excluding namespace blocks) or in a function parameter list has block scope. Block scope extends from the declaration to the enclosing right brace.

  2. Function Scope. 

    A label is an identifier followed by a colon (:). Labels in C/C++ functions have their own scope. They are accessible before and after their declaration, throughout the function definition. C and C++ support a very rarely used and (justifiably) shunned goto statement that requires a label. The thing that makes its scope unique is that the label (i.e., the self-declaration) can appear after the first statement (e.g., goto) that refers to it. Example 20.3 shows an example of the use of the strongly deprecated goto statement and its related label.

    Example 20.3. src/goto/goto.cpp

    [ . . . . ]
    int look() {
        int i=0;
        for (i=0; i<10; ++i) {
            if (i == rand() % 20)
                goto found; 1
        }
        return -1;
    found:                  2
        return i;
    }
    [ . . . . ]
    

    1

    It would be better to use break or continue.

    2

    goto serves as a forward declaration for a label.


    A related but less dangerous use of labels is in the switch statement block. The switch statement is a computed goto statement that, because its action is contained within a single block, does not create the same kinds of validation problems that goto can create. Example 20.4 shows an example of switch usage.

    Example 20.4. src/switch/switchdemo.cpp

    #include <QTextStream>
    #include "usermanager.h"
    
    QTextStream cout(stdout);
    QTextStream cin(stdin);
    enum  Choices {LOAD = 1, ADD, CHANGE, CHECK, SAVE, LIST, QUIT}; 1
    
    // Function Prototypes
    void addUsers(UserManager&);
    void changePassword(UserManager&);
    Choices menu();
    
    //etc.
    
    int main() {
      // some code omitted
      while (1) {
        switch (menu()) {                                           2
        case LOAD:  
          cout << "Reading from file ...\n"
    	   << um.loadList() << " loaded to list"
    	   << endl;
          break;
        case ADD:
          cout << "Adding users to the list ..." << endl;
          addUsers(um);
          break;
        case SAVE:
          cout << "Saving changes ...\n"
    	   << um.saveList() << " users in file" << endl;
          break;
        case CHANGE:
          cout << "Changing password ..." << endl;
          changePassword(um);
          break;
        case CHECK:
          cout << "Checking a userid/pw combo ..." << endl;
          checkUser(um);
          break;
        case LIST:
          cout << "Listing users and passwords ...\n";
          um.listUsers();
          break;
        case QUIT:
          cout << "Exiting the program ..." << endl;
          return 0;
        default:
          cout << "Invalid choice! " << endl;
        }
      }
    }
    

    1

    enums are discussed in Chapter 19.

    2

    menu() obtains a value from the user.


    As we observed in Section 19.2.2, case labels (other than default) differ from ordinary labels because they are required to be integral constants. The scope of a case label is the entire switch statement so you might call it switch scope.

    Labels are sometimes used to solve various compatability problems. For example, labels are used to prevent the C++ compiler from choking on the signals: and slots: declarations in certain class definitions (Section 8.3).

    [Note] Note

    Even though goto is part of the C++ language, you should not use it.

  3. Namespace Scope. 

    An identifier declared inside a namespace has namespace scope. It can be used anywhere, below the declaration, inside the namespace definition. namespace definitions are open and can be expanded. A subsequent definition of the same namespace simply adds items to it, as you can see in Example 20.5. Any attempt to redefine items inside the namespace produces a compile error.

    Example 20.5. src/namespace/openspace/opendemo.txt

    //File:  a.h"
    #ifndef _A_H_
    #define _A_H_
    
    #include <iostream>
    namespace A {
      using namespace std;
      void f() { cout << "f from A\n"; }
      void g() { cout << "g from A\n"; }
    }
    #endif
    
    //File:  new-a.h
    #ifndef NEW_A_H_
    #define NEW_A_H_
    #include <iostream>
    
    namespace A {
      //void k() { h(); }                      1
      //void g() { cout << "Redefine g()/n"; } 2
      void h() { 
        cout << "h from newA\n";
        g();
      }
    }
    #endif
    
    File: opendemo.cpp
    #include "a.h"
    #include "new-a.h"
    
    int main() {
      using namespace A;
      f();
      h();
    }
    
    /*Run
    
    openspace> ./a.out
    f from A
    h from newA
    g from A
    openspace>
    
    */
    

    1

    Error!

    2

    Error!


  4. Class Scope. 

    An identifier declared inside a class definition has class scope. Class Scope is anywhere in the class definition [110] or in the bodies of member functions. [111]

  5. File Scope. 

    An identifier whose declaration is not between curly braces can have file scope if it is declared static. Its scope extends from the declaration to the end of the file. The keyword static hides the identifier from other source files and restricts its scope to the file in which it was declared. File Scope variables cannot be declared extern and accessed from another file.

    File Scope variables, because they are not exported, do not expand (pollute) the global namespace. They are often used in C programs because C does not have an implementation hiding feature like private for class members.

    [Note] Note

    File scope is available in C++ for backward compatibility with C, but namespaces or static class members should be used instead whenever possible.

  6. Global Scope. 

    An identifier whose declaration is not between curly braces, and is not declared static, is said to have global scope. The scope of such an identifier begins at the declaration and extends from there to the bottom of the source code file, but it can be extended to other source files with the use of the extern keyword. The extern declaration may be used to access a globally defined identifier in other source files.

    Use of global scope for variables is unnecessary in C++. In general, only classes and namespaces should be defined in global scope. If you need a "global" variable, you can achieve something similar through the use of a public static class member or a namespace member. Because a compiler deals with one source file at a time, only the linker (or a template-compiler) makes a strong distinction between global and file scope, as Example 20.6 shows.

    Example 20.6.  Global Versus File Scope

    In file 1:

    int g1;        // global
    int g2;        // global
    static int g3; // keyword static limits g3 to file scope
    (etc.)

    In file 2:

    int g1;           // linker error!
    extern int g2;    // OK, share variable space
    static int g3;    // okay, 2 different variable spaces
    (etc.)

    An identifier in a namespace can be made available globally through the use of the scope resolution operator, NamespaceName::. It can also be made available to other scopes without using scope resolution through the using keyword.

    Namespace variables and static class members have static storage and can be made accessible globally. They are like global variables, except that they do not enlarge (pollute) the global namespace. See Section 20.4 for more details.



[110] Including inline function definitions above the declaration of referred members.

[111] Keeping in mind that the scope of non-static members excludes the bodies of static member functions.