|

As
you’ve learned, clients are the parts of the program
that create and use objects of your class. You can
think of the interface to your class—the class declaration—as
a contract with these clients. The contract tells
what data your class has available and how your
class will behave.
For
example, in the Cat class declaration, you create
a contract that every Cat will have a member variable
itsAge that can be initialized in its constructor,
assigned to by its SetAge() accessor function, and
read by its GetAge() accessor. You also promise
that every Cat will know how to Meow().
If
you make GetAge() a const function—as you should—the
contract also promises that GetAge() won’t change
the Cat on which it is called.
C++
is strongly typed, which means that the compiler
enforces these contracts by giving you a compiler
error when you violate them. Listing 6.5 demonstrates
a program that doesn’t compile because of violations
of these contracts.
WARNING: Listing 6.5 does not compile!
Listing
6.5. A demonstration of violations of the interface.
1:
// Demonstrates compiler errors
2:
3:
4:
#include <iostream.h> // for cout
5:
6:
class Cat
7:
{
8:
public:
9:
Cat(int initialAge);
10:
~Cat();
11:
int GetAge() const; // const accessor function
12:
void SetAge (int age);
13:
void Meow();
14:
private:
15:
int itsAge;
16:
};
17:
18:
// constructor of Cat,
19:
Cat::Cat(int initialAge)
20:
{
21:
itsAge = initialAge;
21:
cout << "Cat Constructor\n";
22:
}
23:
24:
Cat::~Cat() // destructor, takes no action
25:
{
26:
cout << "Cat Destructor\n";
27:
}
28:
// GetAge, const function
29:
// but we violate const!
30:
int Cat::GetAge() const
31:
{
32:
return (itsAge++); // violates const!
33:
}
34:
35:
// definition of SetAge, public
36:
// accessor function
37:
38:
void Cat::SetAge(int age)
39:
{
40:
// set member variable its age to
41:
// value passed in by parameter age
42:
itsAge = age;
43:
}
44:
45:
// definition of Meow method
46:
// returns: void
47:
// parameters: None
48:
// action: Prints "meow" to screen
49:
void Cat::Meow()
50:
{
51:
cout << "Meow.\n";
52:
}
53:
54:
// demonstrate various violations of the
55
// interface, and resulting compiler errors
56:
int main()
57:
{
58:
Cat Frisky; // doesn’t match declaration
59:
Frisky.Meow();
60:
Frisky.Bark(); // No, silly, cat’s can’t bark.
61:
Frisky.itsAge = 7; // itsAge is private
62:
return 0;
63:
}
Analysis: As it is written, this program
doesn’t compile. Therefore, there is no output.
This program was fun to write because there are
so many errors in it.
Line
11 declares GetAge() to be a const accessor function—as
it should be. In the body of GetAge(), however,
in line 32, the member variable itsAge is incremented.
Because this method is declared to be const, it
must not change the value of itsAge. Therefore,
it is flagged as an error when the program is compiled.
In
line 13, Meow() is not declared const. Although
this is not an error, it is bad programming practice.
A better design takes into account that this method
doesn’t change the member variables of Cat. Therefore,
Meow() should be const.
Line
58 shows the definition of a Cat object, Frisky.
Cats now have a constructor, which takes an integer
as a parameter. This means that you must pass in
a parameter. Because there is no parameter in line
58, it is flagged as an error.
Line
60 shows a call to a class method, Bark(). Bark()
was never declared. Therefore, it is illegal.
Line
61 shows itsAge being assigned the value 7. Because
itsAge is a private data member, it is flagged as
an error when the program is compiled.
Why
Use the Compiler to Catch Errors?
While
it would be wonderful to write 100 percent bug-free
code, few programmers have been able to do so. However,
many programmers have developed a system to help
minimize bugs by catching and fixing them early
in the process. Although compiler errors are infuriating
and are the bane of a programmer’s existence, they
are far better than the alternative. A weakly typed
language enables you to violate your contracts without
a peep from the compiler, but your program will
crash at run-time—when, for example, your boss is
watching. Compile-time errors—that is, errors found
while you are compiling—are far better than run-time
errors—that is, errors found while you are executing
the program. This is because compile-time errors
can be found much more reliably. It is possible
to run a program many times without going down every
possible code path. Thus, a run-time error can hide
for quite a while. Compile-time errors are found
every time you compile. Thus, they are easier to
identify and fix. It is the goal of quality programming
to ensure that the code has no runtime bugs. One
tried-and-true technique to accomplish this is to
use the compiler to catch your mistakes early in
the development process.
Where
to Put Class Declarations and Method Definitions
Each
function that you declare for your class must have
a definition. The definition is also called the
function implementation. Like other functions, the
definition of a class method has a function header
and a function body.
The
definition must be in a file that the compiler can
find. Most C++ compilers want that file to end with
.C or .CPP. This book uses .CPP, but check your
compiler to see what it prefers.
NOTE: Many compilers assume that files
ending with .C are C programs, and that C++ program
files end with .CPP. You can use any extension,
but .CPP will minimize confusion.
You
are free to put the declaration in this file as
well, but that is not good programming practice.
The convention that most programmers adopt is to
put the declaration into what is called a header
file, usually with the same name but ending in .H,
.HP, or .HPP. This book names the header files with
.HPP, but check your compiler to see what it prefers.
For
example, you put the declaration of the Cat class
into a file named CAT.HPP, and you put the definition
of the class methods into a file called CAT.CPP.
You then attach the header file to the .CPP file
by putting the following code at the top of CAT.CPP:
#include
Cat.hpp
This
tells the compiler to read CAT.HPP into the file,
just as if you had typed in its contents at this
point. Why bother separating them if you’re just
going to read them back in? Most of the time, clients
of your class don’t care about the implementation
specifics. Reading the header file tells them everything
they need to know; they can ignore the implementation
files.
NOTE: The declaration of a class tells
the compiler what the class is, what data it holds,
and what functions it has. The declaration of the
class is called its interface because it tells the
user how to interact with the class. The interface
is usually stored in an .HPP file, which is referred
to as a header file. The function definition tells
the compiler how the function works. The function
definition is called the implementation of the class
method, and it is kept in a .CPP file. The implementation
details of the class are of concern only to the
author of the class. Clients of the class—that is,
the parts of the program that use the class—don’t
need to know, and don’t care, how the functions
are implemented.
|