7.4.1. Name Lookup and Class Scope

Image

In the programs we’ve written so far, name lookup (the process of finding which declarations match the use of a name) has been relatively straightforward:

• 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.

• If the name isn’t found, look in the enclosing scope(s).

• If no declaration is found, then the program is in error.

The way names are resolved inside member functions defined inside the class may seem to behave differently than these lookup rules. However, in this case, appearances are deceiving. Class definitions are processed in two phases:

• First, the member declarations are compiled.

• Function bodies are compiled only after the entire class has been seen.


Image Note

Member function definitions are processed after the compiler processes all of the declarations in the class.


Classes are processed in this two-phase way to make it easier to organize class code. Because member function bodies are not processed until the entire class is seen, they can use any name defined inside the class. If function definitions were processed at the same time as the member declarations, then we would have to order the member functions so that they referred only to names already seen.

Name Lookup for Class Member Declarations

This two-step process applies only to names used in the body of a member function. Names used in declarations, including names used for the return type and types in the parameter list, must be seen before they are used. If a member declaration uses a name that has not yet been seen inside the class, the compiler will look for that name in the scope(s) in which the class is defined. For example:

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

When the compiler sees the declaration of the balance function, it will look for a declaration of Money in the Account class. The compiler considers only declarations inside Account that appear before the use of Money. Because no matching member is found, the compiler then looks for a declaration in the enclosing scope(s). In this example, the compiler will find the typedef of Money. That type will be used for the return type of the function balance and as the type for the data member bal. On the other hand, the function body of balance is processed only after the entire class is seen. Thus, the return inside that function returns the member named bal, not the string from the outer scope.

Type Names Are Special

Ordinarily, an inner scope can redefine a name from an outer scope even if that name has already been used in the inner scope. However, in a class, if a member uses a name from an outer scope and that name is a type, then the class may not subsequently redefine that name:

typedef double Money;
class Account {
public:
    Money balance() { return bal; }  // uses Money from the outer scope
private:
    typedef double Money; // error: cannot redefine Money
    Money bal;
    // ...
};

It is worth noting that even though the definition of Money inside Account uses the same type as the definition in the outer scope, this code is still in error.

Although it is an error to redefine a type name, compilers are not required to diagnose this error. Some compilers will quietly accept such code, even though the program is in error.


Image Tip

Definitions of type names usually should appear at the beginning of a class. That way any member that uses that type will be seen after the type name has already been defined.


Normal Block-Scope Name Lookup inside Member Definitions

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

• First, look for a declaration of the name inside the member function. As usual, only declarations in the function body that precede the use of the name are considered.

• If the declaration is not found inside the member function, look for a declaration inside the class. All the members of the class are considered.

• If a declaration for the name is not found in the class, look for a declaration that is in scope before the member function definition.

Ordinarily, it is a bad idea to use the name of another member as the name for a parameter in a member function. However, in order to show how names are resolved, we’ll violate that normal practice in our dummy_fcn function:

// note: this code is for illustration purposes only and reflects bad practice
// it is generally a bad idea to use the same name for a parameter and a member
int height;   // defines a name subsequently used inside Screen
class Screen {
public:
    typedef std::string::size_type pos;
    void dummy_fcn(pos height) {
        cursor = width * height; // which height? the parameter
    }
private:
    pos cursor = 0;
    pos height = 0, width = 0;
};

When the compiler processes the multiplication expression inside dummy_fcn, it first looks for the names used in that expression in the scope of that function. A function’s parameters are in the function’s scope. Thus, 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. If we wanted to override the normal lookup rules, we can do so:

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


Image Note

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


A much better way to ensure that we get the member named height would be to give the parameter a different name:

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

In this case, when the compiler looks for the name height, it won’t be found inside dummy_fcn. The compiler next looks at all the declarations in Screen. 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, the name height is defined in the outer scope before the definition of Screen. However, the object in the outer scope is hidden by our member named height. If we want the name from the outer scope, we can ask for it explicitly using the scope operator:

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


Image Note

Even though the outer object is hidden, it is still possible to access that object by using the scope operator.


Names Are Resolved Where They Appear within a File

When a member is defined outside its class, the third step of name lookup includes names declared in the scope of the member definition as well as those that appear in the scope of the class definition. For example:

int height;   // defines a name subsequently used inside Screen
class Screen {
public:
    typedef std::string::size_type pos;
    void setHeight(pos);
    pos height = 0;  // hides the declaration of height in the outer scope
};
Screen::pos verify(Screen::pos);
void Screen::setHeight(pos 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 includes the scope in which the member definition appears. In this example, the declaration for verify appears before setHeight is defined and may, therefore, be used.


Exercises Section 7.4.1

Exercise 7.34: What would happen if we put the typedef of pos in the Screen class on page 285 as the last line in the class?

Exercise 7.35: Explain the following code, indicating which definition of Type or initVal is used for each use of those names. Say how you would fix any errors.

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();
    return val;
}


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.157.6