Placing a Class in a Separate File for Reusability
We have developed class GradeBook as far as we need to for now from a programming perspective, so let's consider some software engineering issues. One of the benefits of creating class definitions is that, when packaged properly, our classes can be reused by programmerspotentially worldwide. For example, we can reuse C++ Standard Library type string in any C++ program by including the header file in the program (and, as we will see, by being able to link to the library's object code).
Unfortunately, programmers who wish to use our GradeBook class cannot simply include the file from Fig. 3.7 in another program. As you learned in Chapter 2, function main begins the execution of every program, and every program must have exactly one main function. If other programmers include the code from Fig. 3.7, they get extra baggageour main functionand their programs will then have two main functions. When they attempt to compile their programs, the compiler will indicate an error because, again, each program can have only one main function. For example, attempting to compile a program with two main functions in Microsoft Visual C++ .NET produces the error
error C2084: function 'int main(void)' already has a body
when the compiler tries to compile the second main function it encounters. Similarly, the GNU C++ compiler produces the error
redefinition of 'int main()'
These errors indicate that a program already has a main function. So, placing main in the same file with a class definition prevents that class from being reused by other programs. In this section, we demonstrate how to make class GradeBook reusable by separating it into another file from the main function.
Header Files
Each of the previous examples in the chapter consists of a single .cpp file, also known as a source-code file, that contains a GradeBook class definition and a main function. When building an object-oriented C++ program, it is customary to define reusable source code (such as a class) in a file that by convention has a .h filename extensionknown as a header file. Programs use #include preprocessor directives to include header files and take advantage of reusable software components, such as type string provided in the C++ Standard Library and user-defined types like class GradeBook.
In our next example, we separate the code from Fig. 3.7 into two filesGradeBook.h (Fig. 3.9) and fig03_10.cpp (Fig. 3.10). As you look at the header file in Fig. 3.9, notice that it contains only the GradeBook class definition (lines 1141) and lines 38, which allow class GradeBook to use cout, endl and type string. The main function that uses class GradeBook is defined in the source-code file fig03_10.cpp (Fig. 3.10) at lines 1021. To help you prepare for the larger programs you will encounter later in this book and in industry, we often use a separate source-code file containing function main to test our classes (this is called a driver program). You will soon learn how a source-code file with main can use the class definition found in a header file to create objects of a class.
Figure 3.9. GradeBook class definition.
(This item is displayed on pages 96 - 97 in the print version)
1 // Fig. 3.9: GradeBook.h 2 // GradeBook class definition in a separate file from main. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include // class GradeBook uses C++ standard string class 8 using std::string; 9 10 // GradeBook class definition 11 class GradeBook 12 { 13 public: 14 // constructor initializes courseName with string supplied as argument 15 GradeBook( string name ) 16 { 17 setCourseName( name ); // call set function to initialize courseName 18 } // end GradeBook constructor 19 20 // function to set the course name 21 void setCourseName( string name ) 22 { 23 courseName = name; // store the course name in the object 24 } // end function setCourseName 25 26 // function to get the course name 27 string getCourseName() 28 { 29 return courseName; // return object's courseName 30 } // end function getCourseName 31 32 // display a welcome message to the GradeBook user 33 void displayMessage() 34 { 35 // call getCourseName to get the courseName 36 cout << "Welcome to the grade book for " << getCourseName() 37 << "!" << endl; 38 } // end function displayMessage 39 private: 40 string courseName; // course name for this GradeBook 41 }; // end class GradeBook |
Including a Header File That Contains a User-Defined Class
A header file such as GradeBook.h (Fig. 3.9) cannot be used to begin program execution, because it does not contain a main function. If you try to compile and link GradeBook.h by itself to create an executable application, Microsoft Visual C++ .NET will produce the linker error message:
error LNK2019: unresolved external symbol _main referenced in function _mainCRTStartup
Running GNU C++ on Linux produces a linker error message containing:
undefined reference to 'main'
This error indicates that the linker could not locate the program's main function. To test class GradeBook (defined in Fig. 3.9), you must write a separate source-code file containing a main function (such as Fig. 3.10) that instantiates and uses objects of the class.
Figure 3.10. Including class GradeBook from file GradeBook.h for use in main.
1 // Fig. 3.10: fig03_10.cpp 2 // Including class GradeBook from file GradeBook.h for use in main. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include "GradeBook.h" // include definition of class GradeBook 8 9 // function main begins program execution 10 int main() 11 { 12 // create two GradeBook objects 13 GradeBook gradeBook1( "CS101 Introduction to C++ Programming" ); 14 GradeBook gradeBook2( "CS102 Data Structures in C++" ); 15 16 // display initial value of courseName for each GradeBook 17 cout << "gradeBook1 created for course: " << gradeBook1.getCourseName() 18 << " gradeBook2 created for course: " << gradeBook2.getCourseName() 19 << endl; 20 return 0; // indicate successful termination 21 } // end main
|
Recall from Section 3.4 that, while the compiler knows what fundamental data types like int are, the compiler does not know what a GradeBook is because it is a user-defined type. In fact, the compiler does not even know the classes in the C++ Standard Library. To help it understand how to use a class, we must explicitly provide the compiler with the class's definitionthat's why, for example, to use type string, a program must include the header file. This enables the compiler to determine the amount of memory that it must reserve for each object of the class and ensure that a program calls the class's member functions correctly.
To create GradeBook objects gradeBook1 and gradeBook2 in lines 1314 of Fig. 3.10, the compiler must know the size of a GradeBook object. While objects conceptually contain data members and member functions, C++ objects typically contain only data. The compiler creates only one copy of the class's member functions and shares that copy among all the class's objects. Each object, of course, needs its own copy of the class's data members, because their contents can vary among objects (such as two different BankAccount objects having two different balance data members). The member function code, however, is not modifiable, so it can be shared among all objects of the class. Therefore, the size of an object depends on the amount of memory required to store the class's data members. By including GradeBook.h in line 7, we give the compiler access to the information it needs (Fig. 3.9, line 40) to determine the size of a GradeBook object and to determine whether objects of the class are used correctly (in lines 1314 and 1718 of Fig. 3.10).
Line 7 instructs the C++ preprocessor to replace the directive with a copy of the contents of GradeBook.h (i.e., the GradeBook class definition) before the program is compiled. When the source-code file fig03_10.cpp is compiled, it now contains the GradeBook class definition (because of the #include), and the compiler is able to determine how to create GradeBook objects and see that their member functions are called correctly. Now that the class definition is in a header file (without a main function), we can include that header in any program that needs to reuse our GradeBook class.
How Header Files Are Located
Notice that the name of the GradeBook.h header file in line 7 of Fig. 3.10 is enclosed in quotes (" ") rather than angle brackets (< >). Normally, a program's source-code files and user-defined header files are placed in the same directory. When the preprocessor encounters a header file name in quotes (e.g., "GradeBook.h"), the preprocessor attempts to locate the header file in the same directory as the file in which the #include directive appears. If the preprocessor cannot find the header file in that directory, it searches for it in the same location(s) as the C++ Standard Library header files. When the preprocessor encounters a header file name in angle brackets (e.g., ), it assumes that the header is part of the C++ Standard Library and does not look in the directory of the program that is being preprocessed.
Error-Prevention Tip 3.3
To ensure that the preprocessor can locate header files correctly, #include preprocessor directives should place the names of user-defined header files in quotes (e.g., "GradeBook.h") and place the names of C++ Standard Library header files in angle brackets (e.g., ). |
Additional Software Engineering Issues
Now that class GradeBook is defined in a header file, the class is reusable. Unfortunately, placing a class definition in a header file as in Fig. 3.9 still reveals the entire implementation of the class to the class's clientsGradeBook.h is simply a text file that anyone can open and read. Conventional software engineering wisdom says that to use an object of a class, the client code needs to know only what member functions to call, what arguments to provide to each member function and what return type to expect from each member function. The client code does not need to know how those functions are implemented.
If client code does know how a class is implemented, the client code programmer might write client code based on the class's implementation details. Ideally, if that implementation changes, the class's clients should not have to change. Hiding the class's implementation details makes it easier to change the class's implementation while minimizing, and hopefully eliminating, changes to client code.
In Section 3.9, we show how to break up the GradeBook class into two files so that
- the class is reusable
- the clients of the class know what member functions the class provides, how to call them and what return types to expect
- the clients do not know how the class's member functions are implemented.
Категории