Introduction:

The actual implementations of our classes make up the bulk of our code base. The implementation files contain function definitions for all methods declared in header files. Similar to what we observed in Section 1 for header files, implementation files obey strict conventions for file naming and code style. Some good examples of implementation files are:

the copy constructor for the Boolean class (basic - no templates);
all of the required public methods for the MVector class including constructors (intermediate - templates);
a typical algorithm implementation using virtual functions and interface contracts (advanced).

We have two standards for implementation files. For non-template classes, all implementation files are named according to either the section of the class to which they correspond (that is, the section name given in the comments), or the name of the method if it is the only one in the section. In general, methods with the same name are placed in the same file. Template classes follow the same file rules as non-template classes except for the templatized functions which are placed in a file named ClassName_00.cc (where "ClassName" is the name of the class).

In this section, we will explain the internal structure of our implementation files, method definitions, and scope issues. You can find a good overview of class implementations in Lippman and Lajoie, Chapter 13.

Functions:

Our philosophy in programming in C++ is that we should not support any stand-alone functions: all methods are functions, but not all functions are methods, unless you are working in the ISIP environment. A method is a special function that exists as part of a class. Methods are implemented a little differently than free-roaming functions. The copy constructor for Boolean is a good example of a non-templatized method, and the constructors for MVector are good examples of templatized methods.

For example, consider the prototype for the Boolean copy constructor: Notice that the name Boolean is repeated across the double colon. The first name, "Boolean," is naming the class to which this method belongs. The second name, "Boolean," is naming the method itself. This might be clearer for some other method, say readData: The name Boolean appears repeatedly, but it's clearer in this example that Boolean::readData means that this definition of readData is a method of the Boolean class.

For template classes, the prototype looks almost the same, but with a template in tow. Using what you've already read about templates, see if you can find the name of this function and the class to which it belongs: This is the required public method name(), which returns the name of its class. This particular name() belongs to the MVector class. You have probably already found the template for the class - it's sitting on top of the prototype. Note that the name of the class in this prototype includes the template parameters, just as if you were instantiating an object of the class. If you didn't include the parameters with the class name of a template method, the compiler wouldn't know which version of name() to call. So will return an error during compilation. In fact, if the compiler allowed this, think of all the bad things that would happen when you ran the program. Every templatized method of a template class must include the template and templatized class name in its function definition as above.

For more information on method implementations, see Lippman and Lajoie, Section 13.3.

Arguments:

As we said before, C++ is a strongly-typed language, so you must either pass methods objects of the type specified in the function prototype, or, in the case of call by value, provide a way for the object you are passing to be converted to the type the method expects. This strong-typing extends beyond normal objects and into a whole range of argument passing instruments. The most widely used of these instruments is passing by reference.

When you call a function this way: dbg is copied, and that copy is given to the scope of myBool.diagnose(). Although an integral type or enumeration, such as Integral::DEBUG, requires a small amount of memory, passing a complex object to a function by value could turn out to be extremely inefficient. You don't want to copy very large objects or pass them around in memory, so instead we pass the method a reference to the original object. Now our method can see the data we want it to see, and use the reference as though it were the original object. Further, the object need not be passed as a pointer, so all those Fortran-programming speech researchers remain extremely happy.

A function declaration for a function that accepts reference arguments looks like this: Notice that the argument type isn't just Boolean, but Boolean&. The ampersand indicates the use of a reference argument - referred to as call by reference. Invoking this function uses standard call by value syntax: One downside to using reference arguments is that the original object can be modified from within the method. To prevent the method from modifying the object passed by reference, we place the const keyword in the argument list as discussed in Function Prototypes. The const keyword protects the original object by making the method see it as a constant.

For more information on function arguments, see Lippman and Lajoie, Section 7.2.2.

Scope:

Scope is the window through which code can see data. In C++ the borders of this window are the curly braces that define a code block. Each set of curly braces redefines how much data your code can see. We typically use two scopes: class scope and method scope. While many other levels of scope exist, we choose not to use them, and thus avoid ambiguity in our naming scheme.

So what do these scopes mean? Class scope means that an object of a class is the only object that can access its member data. Other objects of the class have their own copies of the member data defined in the class definition. To reiterate, in class scope, any class object can only see its own member data. Method scope exists inside of class scope. Methods are defined between curly braces, but only exist inside of classes, so they have access (by the bottom-up rule) to the member data of their class (or object once they are instantiated). A method can access its class' member data just by using the member's name: As previously seen in the Boolean class, value_d is one of the protected members of the Boolean class, so methods of the Boolean class can modify it directly.

Scope also applies to methods. In most cases, you can only call methods of a class through on object of that class. Like the assign() method in the previous section, methods must be called by an object: A name can only be used once per scope in C++. Since methods are in the class scope, and thus separated from all other scopes, means function names can be reused across classes (and as you've seen, they are). If we didn't use classes, we would be at the mercy of the global scope, and we would need unique names for all the hundreds of functions in the ISIP environment. So, we don't use the global scope at all, and thus our names are all safe.

For a more detailed look at scope, see Lippman and Lajoie, Section 8.1. For a focused look at the global scope, see Lippman and Lajoie, Section 8.2.

Static Functions:

This is the part where we tell you how to get around scope. Earlier we mentioned a keyword called static and how this keyword made constants exist only once for every instance of their class (see Constants). Well, that's not the end of the story. Functions may also be declared static, and like constants, only one copy of the function will exist for all instances of the class. For functions, this also means that programmers can call the function without creating an object of the class. For an example, see the exit() method of Integral, or the diagnose() method of any class. You should also read the example main program at the bottom of the Shell library documentation.

At the end of the example program for Shell, you see the following line: However, if you search through the main program, you won't see any instance of the Integral class. Because exit() is declared as a static method, it can be called directly out of Integral's scope. Static method declarations look like this: Only the function declaration needs the static keyword. The prototype of the definition looks like this: Our environment uses static functions extensively to provide a practical operating system interface to programmers as well as to our classes.

For more information on static functions, see Lippman and Lajoie, Section 13.5.1.