Section 12.3. Class Scope


12.3. Class Scope

Every class defines its own new scope and a unique type. The declarations of the class members within the class body introduce the member names into the scope of their class. Two different classes have two different class scopes.

Even if two classes have exactly the same member list, they are different types. The members of each class are distinct from the members of any other class (or any other scope).



For example:

      class First {      public:          int memi;          double memd;      };      class Second {      public:          int memi;          double memd;      };      First obj1;      Second obj2 = obj1; // error: obj1 and obj2 have different types 

Using a Class Member

Outside the class scope, members may be accessed only through an object or a pointer using member access operators dot or arrow, respectively. The left-hand operand to these operators is a class object or a pointer to a class object, respectively. The member name that follows the operator must be declared in the scope of the associated class:

      Class obj;     // Class is some class type      Class *ptr = &obj;      // member is a data member of that class      ptr->member;   // fetches member from the object to which ptr points      obj.member;    // fetches member from the object named obj      // memfcn is a function member of that class      ptr->memfcn(); // runs memfcn on the object to which ptr points      obj.memfcn();  // runs memfcn on the object named obj 

Some members are accessed using the member access operators; others are accessed directly from the class using the scope operator, (::). Ordinary data or function members must be accessed through an object. Members that define types, such as Screen::index, are accessed using the scope operator.

Scope and Member Definitions

Member definitions behave as if they are in the scope of the class, even if the member is defined outside the class body. Recall that member definitions that appear outside the class body must indicate the class in which the member appears:

      double Sales_item::avg_price() const      {          if (units_sold)              return revenue/units_sold;          else              return 0;      } 

Here we use the fully qualified name Sales_item::avg_price to indicate that the definition is for the avg_price member in the scope of the Sales_item class. Once the fully qualified name of the member is seen, the definition is known to be in class scope. Because the definition is in class scope, we can refer to revenue and units_sold without having to write this->revenue or this->units_sold.

Parameter Lists and Function Bodies Are in Class Scope

In a member function defined outside the class, the parameter list and member-function body both appear after the member name. These are defined inside the class scope and so may refer to other class members without qualificationfor example, the definition of the two-parameter version of get in class Screen:

      char Screen::get(index r, index c) const      {          index row = r * width;      // compute the row location          return contents[row + c];   // offset by c to fetch specified character      } 

This function uses the type name index defined inside Screen to name the types of its parameters. Because the parameter list is inside the scope of class Screen, there is no need to specify that we want Screen::index. It is implicit that the one we want is the one defined in the current class scope. Similarly, the uses of index, width, and contents all refer to names declared inside class Screen.

Function Return Types Aren't Always in Class Scope

In contrast to the parameter types, the return type appears before the member name. If the function is defined outside the class body, then the name used for the return type is outside the class scope. If the return type uses a type defined by the class, it must use the fully qualified name. For example, consider the get_cursor function:

      class Screen {      public:          typedef std::string::size_type index;          index get_cursor() const;      };      inline Screen::index Screen::get_cursor() const      {          return cursor;      } 

The return type of this function is index, which is a type name defined inside the Screen class. If we define get_cursor outside the class body, the code is not in the class scope until the function name has been processed. When the return type is seen, its name is used outside of the class scope. We must use the fully qualified type name, Screen::index to specify that we want the name index that is defined inside class Screen.

Exercises Section 12.3

Exercise 12.15:

List the portions of program text that are in class scope.

Exercise 12.16:

What would happen if we defined get_cursor as follows:

      index Screen::get_cursor() const      {          return cursor;      } 


12.3.1. Name Lookup in Class Scope

In the programs we've written so far, name lookup (the process of finding which declaration is matched to a given use of a name) has been relatively straightforward:

  1. First, look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered.

  2. If the name isn't found, the enclosing scope(s) are searched.

If no declaration is found, then the program is in error. In C++ programs, all names must be declared before they are used.

Class scopes may seem to behave a bit differently, but in reality they obey this same rule. Confusion can arise due to the way names are resolved inside a function defined within the class body itself.

Class definitions are actually processed in two phases:


  1. First, the member declarations are compiled.

  2. Only after all the class members have been seen are the definitions themselves compiled.


Of course, the names used in class scope do not always have to be class member names. Name lookup in class scope finds names declared in other scopes as well. During name lookup, if a name used in class scope does not resolve to a class member name, the scopes surrounding the class or member definition are searched to find a declaration for the name.

Name Lookup for Class Member Declarations

Names used in the declarations of a class member are resolved as follows:

  1. The declarations of the class members that appear before the use of the name are considered.

  2. If the lookup in step 1 is not successful, the declarations that appear in the scope in which the class is defined, and that appear before the class definition itself, are considered.

For example:

      typedef double Money;      class Account {      public:          Money balance() { return bal; }      private:          Money bal;          // ...      }; 

When processing the declaration of the balance function, the compiler first looks for a declaration of Money in the scope of the class Account. The compiler considers only declarations that appear before the use of Money. Because no member declaration is found, the compiler then looks for a declaration of Money in global scope. Only the declarations located before the definition of the class Account are considered. The declaration for the global typedef Money is found and is used for the return type of the function balance and the data member bal.

Names of types defined in a class must be seen before they are used as the type of a data member or as the return type or parameter type(s) of a member function.



The compiler handles member declarations in the order in which they appear in the class. As usual, a name must be defined before it can be used. Moreover, once a name has been used as the name of a type, that name may not be redefined:

      typedef double Money;      class Account {      public:          Money balance() { return bal; } // uses global definition of Money      private:          // error: cannot change meaning of Money          typedef long double Money;          Money bal;          // ...      }; 

Name Lookup in Class Member Definitions

A name used in the body of a member function is resolved as follows:

  1. Declarations in the member-function local scopes are considered first.

  2. If the a declaration for the name is not found in the member function, the declarations for all the class members are considered.

  3. If a declaration for the name is not found in the class, the declarations that appear in scope before the member function definition are considered.

Class Members Follow Normal Block-Scope Name Lookup

Programs that illustrate how name lookup works often have to rely on bad practices. The next several programs contain bad style deliberately.



The following function uses the same name for a parameter and a member, which normally should be avoided. We do so here to show how names are resolved:

      // Note: This code is for illustration purposes only and reflects bad practice      // It is a bad idea to use the same name for a parameter and a member      int height;      class Screen {      public:          void dummy_fcn(index height) {              cursor = width * height; // which height? The parameter          }      private:          index cursor;          index height, width;      }; 

When looking for a declaration for the name height used in the definition of dummy_fcn, the compiler first looks in the local scope of that function. A function parameter is declared in the local scope of its function. The name height used in the body of dummy_fcn refers to this parameter declaration.

In this case, the height parameter hides the member named height.

Even though the class member is hidden, it is still possible to use it by qualifying the member's name with the name of its class or by using the this pointer explicitly.



If we wanted to override the normal lookup rules, we could do so:

      // bad practice: Names local to member functions shouldn't hide member names      void dummy_fcn(index height) {          cursor = width * this->height;   // member height          // alternative way to indicate the member          cursor = width * Screen::height; // member height      } 

After Function Scope, Look in Class Scope

If we wanted to use the member named height, a much better way to do so would be to give the parameter a different name:

      // good practice: Don't use member name for a parameter or other local variable      void dummy_fcn(index ht) {          cursor = width * height; // member height      } 

Now when the compiler looks for the name height, it will not find that name in the function. The compiler next looks in the Screen class. Because height is used inside a member function, the compiler looks at all the member declarations. Even though the declaration of height appears after its use inside dummy_fcn, the compiler resolves this use to the data member named height.

After Class Scope, Look in the Surrounding Scope

If the compiler doesn't find the name in function or class scope, it looks for the name in the surrounding scope. In our example, declarations in global scope that appear before the definition of the Screen include a global object named height. However, that object is hidden.

Even though the global object is hidden, it is still possible to use it by qualifying the name with the global scope resolution operator.



      // bad practice: Don't hide names that are needed from surrounding scopes      void dummy_fcn(index height) {          cursor = width * ::height;// which height? The global one      } 

Names Are Resolved Where They Appear within the File

When a member is defined outside the class definition, the third step of name lookup not only considers the declarations in global scope that appear before the definition of class Screen, but also considers the global scope declarations that appear before the member function definitionfor example:

      class Screen {      public:          // ...          void setHeight(index);      private:          index height;      };      Screen::index verify(Screen::index);      void Screen::setHeight(index var) {          // var: refers to the parameter          // height: refers to the class member          // verify: refers to the global function          height = verify(var);      } 

Notice that the declaration of the global function verify is not visible before the definition of the class Screen. However, the third step of name lookup considers the surrounding scope declarations that appear before the member definition, and the declaration for the global function verify is found.

Exercises Section 12.3.1

Exercise 12.17:

What would happen if we put the typedef in the Screen class as the last line in the class?

Exercise 12.18:

Explain the following code. Indicate which definition of Type or initVal is used for each use of those names. If there are any errors, say how you would fix the program.

      typedef string Type;      Type initVal();      class Exercise {      public:          // ...          typedef double Type;          Type setVal(Type);          Type initVal();      private:          int val;      };      Type Exercise::setVal(Type parm) {          val = parm + initVal();      } 

The definition of the member function setVal is in error. Apply the necessary changes so that the class Exercise uses the global typedef Type and the global function initVal.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net