3.8. 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 programmers—potentially worldwide. For example, we
can reuse C++ Standard Library type string
in any C++ program by including the header file <string> in the program (and, as we'll 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 baggage—our main function—and their programs
will then have two main functions. When
they attempt to compile their programs, the compiler will indicate an error. For
example, attempting to compile a program with two main functions in Microsoft Visual C++ 2008 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 extension—known 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 files—GradeBook.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 11–41) and lines 3–8, 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) in lines 10–21. To help you prepare for the larger
programs you'll 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'll
soon see how a source-code file with main can use the class definition found in a header file
to create objects of a class.
Fig. 3.9. GradeBook class
definition.
1 // Fig. 3.9: GradeBook.h
2 // GradeBook class definition in a separate file from main.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <string> // 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\n" << getCourseName()
37 << "!" << endl;
38 } // end function displayMessage
39 private:
40 string courseName; // course name for this GradeBook
41 }; // end class GradeBook
|
Fig. 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 <iostream>
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 << "\ngradeBook2 created for course: " << gradeBook2.getCourseName()
19 << endl;
20 return 0; // indicate successful termination
21 } // end main
|
gradeBook1 created for course: CS101 Introduction to C++ Programming
gradeBook2 created for course: CS102 Data Structures in C++
|
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++ 2005 produces the linker error message:
error LNK2019: unresolved external symbol _main referenced in
function _mainCRTStartup
To compile and link with GNU C++ on
Linux, you must first include the header file in a .cpp source-code
file, then GNU C++ 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.
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 definition—that's why, for example, to use type string, a program must include the <string> 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 13–14 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 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 13–14 and 17–18 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.,
<iostream>), 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.,
<iostream>). |
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 clients—GradeBook.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 clients of the class know what member functions
the class provides, how to call them and what return types to expect,
and
-
the clients do not know how the
class's member functions are implemented.