C++

INTERFACE VERSUS IMPLEMENTATION

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.

Back to Index