Reliable Software Logo

C++ In Action: Language

Local scope

Scope of main, passing arguments to constructors, integer type. Private data members, initialization, embedded local scopes, the for loop.


Global scope extends outside of any braces. It is activated (that is, ready to use, all objects constructed) before main is executed, and deactivated (all objects destroyed) after main is exited. Local scopes, on the other hand, are delimited by braces (well, not always, we'll see in a moment that there are cases where braces can be omitted). Local scope is activated when the flow of the program enters it, and deactivated when it leaves it. Objects in local scope are constructed whenever their definition is encountered, and destroyed when the scope is exited. Such objects are also called automatic (because they are automatically created and destroyed), or stack objects (because they occupy memory that is allocated on the program's stack).

Our first example of the use of local scope creates a local World called myWorld within the scope of main(). The myWorld object is constructed right after entering main(), then "Hello from main" is printed, and finally the object is destroyed right before exiting the scope of main. The global World is still constructed before main() and destroyed after main().

Another modification to our original program is the use of an integer as an argument to the constructor of World. An integer is of the predefined type int. Its size is compiler dependent. In the constructor, this argument (called i) is sent to the standard output. Notice how clever the std::cout object is. It accepts strings of characters and prints them as strings and it accepts integers and prints them as decimal numbers. And, as you can see, you can chain arguments to std::cout one after another.

Since the constructor expects an argument, we have to provide it when we create the object. Here we just specify it in parentheses after the name of the object being created

World TheWorld (1);

When the constructor of TheWorld is executed it prints "Hello from 1."

#include <iostream>

class World 
{
public:
    World (int i)
    {
        std::cout << "Hello from " << i << ".\n";
    }

    ~World ()
    {
        std::cout << "Good bye.\n";
    }
};

World TheWorld (1);

int main()
{
    
    World myWorld (2);
    std::cout << "Hello from main!\n";
}

There's still something missing. How do we know which object printed the first "Good bye." and which object printed the second one? Our objects don't remember their identity. In fact they don't remember anything! But before we fix that, let me digress philosophically.

Here we are talking about object oriented programming and I haven't even defined what I mean by an object.

So here we go:

Definition: An object is something that has identity.

If you can think of something that doesn't have an identity, it's not an object. Beauty is not an object. But if you could say "this beauty is different from the one over there," you would give the two beauties their identities and they would become objects. We sometimes define classes that have non-object names. That just means that we give an old name a new "objectified" meaning. In general, though, it's not such a great idea. If possible one should stick to countable nouns.

I didn't mean to say that our Worlds didn't have identity, because they did. They were just not aware of it. Let's give them memory, so that they'll be able to remember who they are.

#include <iostream>

class World
{
public:
    World (int id)
       : _identifier (id)
    {
        std::cout << "Hello from " << _identifier << ".\n";
    }

    ~World ()
    {
        std::cout << "Good bye from " << _identifier << ".\n";
    }
private:
    const int _identifier;
};

World TheWorld (1);

int main ()
{
    World myWorld (2);
    for (int i = 3; i < 6; ++i)
    {
        World aWorld (i);
    }
    World oneMoreWorld (6);
}
						

Several new things require explanation. Let's do it top-down. In main, we create our local World, myWorld, with an id of 2 (this time the World will remember it until it is destroyed). Next we have a for loop. The meaning of it is the following: For integer i starting from 3 as long as it's less than 6, incremented every time the body of the loop is executed (that's the meaning of ++i), do this: Open the scope, define a World called aWorld with an id equal to the current value of i, close the scope.

After the loop is done iterating, one more World is defined called oneMoreWorld. It has the id of 6. The braces delimiting the scope of the for loop may be omitted. This is because here the body of the loop consists of a single statement. So, in fact, we could have written the loop as in Figure 3 and the program would still execute exactly the same way.

Scope, no braces.

Figure 3 A scope without braces.


The body of the for loop forms a separate scope within the scope of main. When the program enters this scope, the objects from the outer scope(s) are not destroyed. In fact, if there is no name conflict, they are still visible and accessible. There would be name conflict if myWorld were called aWorld. It would be perfectly okay, only that the outer aWorld would be temporarily inaccessible within the for loop: the name of the outer aWorld would be hidden by the name of the inner aWorld. We'll see later what exactly accessible means.

We still have our global World, TheWorld, with an id of 1.

The class World has a data member _identifier. Its type is int and it's const and private. Why is it private? None of your business. Okay, okay... It's called data hiding. It means: today it is implemented like this, tomorrow, who knows. Maybe an int, maybe a string, and maybe not at all. If the _identifier weren't private, it would be like with these hidden functions in MS DOS that everybody knew about: Applications started using them and the developers of MS DOS had to keep them forever for compatibility. Not so in our World. The compiler will make sure that nobody, except the World itself, has access to private members.

The keyword const means that nobody can change the value of _identifier during the lifetime of the object. It is physically (or at least "compilatorily") impossible. But even a const has to be initialized some place or another. The compiler provides just one little window when we can (and in fact have to) initialize a const data member. It is in the preamble to the constructor. Not even in the body of the constructor--only in the preamble itself can we initialize a const data member. Preamble is the part of the constructor that starts with a colon and contains a comma-separated list of data members followed by their initializers in parentheses. In our preamble _identifier is initialized with the value of id.

    World (int id)
       : _identifier (id)
    {
        std::cout << "Hello from " << _identifier << ".\n";
    }

Once _identifier is initialized, its value for the particular instance of World never changes (later we'll learn how to modify non-constant data members). When the destructor of this object is called, it will print the value that was passed to it in the constructor. So when we see "Good bye from 1." we'll know that it's the end of TheWorld (as opposed to the end of aWorld).

Why does _identifier have an underscore in front of it? It's a convention. In this book the names of all private data members will start with an underscore. Once you get used to it, it actually helps readability. Of course, you can use your own convention instead. In fact everybody may come up with their own conventions, which is exactly what happens all the time.

To summarize, objects may have their own memory, or state. The state data members are preferably kept private. They are best initialized in the preamble to the constructor, so that the object is in a consistent state immediately after its birth. The for loop creates its own scope. This scope is entered and exited during every iteration (this is why you see all these "Good bye from 3, 4, 5" messages). In fact a matched pair of braces can be put anywhere within another scope, just to form a sub-scope.


NextNext: Embedded Objects