10.2.4. #pragma and #ifndef: Actor 2

Time: 00:06:25 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)
Review

Implementing the previous version of the Actor example in a single file allowed us to focus on the mechanics of inheritance, but putting all the classes in a single file in an object-oriented program isn't common or realistic. The second version still uses the over-simplified Person, Actor, and Star classes but adopts the more common and realistic practice of putting each class specification in a separate header file. This approach increases the potential for reusing classes, but it also increases the potential for unexpected errors.

The classes remain unchanged from the first version, so this section focuses on the syntax necessary to manage a multi-class program. If a class specification only contains member variables and function prototypes (both members and non-members), then the specification is purely a declaration. Multiple declarations don't interfere with compilation if each declaration is the same. But if the class includes any function bodies, then those bodies form a function definition. Multiple declarations don't cause a problem, but multiple definitions do.

Prototypes Only Inline Functions
class Person
{
    private:
        string    name;

    public:
        Person(string n) : name(n);

        void display();
};
class Person
{
    private:
        string    name;

    public:
        Person(string n) : name(n) {} 

        void display() { cout << name << endl; } 
};
(a)(b)
Alternate class specifications: prototypes vs. inline.
  1. The first class specification only contains declarations or prototypes and may be included in a program repeatedly without error. Following this organization, programmers define the constructor and display function in a source code file.
  2. The second class specification contains function bodies (highlighted), making them function definitions. Including this class specification in a program more than once causes a "multiple definition" error when we compile the program.

So far, I haven't suggested how a class specification might become included in a program more than once. The Actor2 header files and the main function demonstrate a common way that this can occur. C++ provides two preprocessor mechanisms (the ANSI standard incorporates only one) to prevent this error by ensuring that a class specification is included in each source code file exactly once.

#include Guard

The classic solution to the multiple file inclusion problem is called an include guard or macro guard. The include guard is implemented by the preprocessor using three directives:

#ifndef
The opening preprocessor directive is read as "if not defined." A symbolic constant name follows the directive. If the name is not defined, the following code is read by the preprocessor and included in the program. Alternatively, if the name is defined, the preprocessor skips all code until it reaches the matching #endif directive. The preprocessor doesn't include the skipped code in the program.
#define
Defines or creates the symbolic constant. If the constant name is already defined, the #ifndef directive skips the definition. The more general usage requires a value to associate with the constant name. As used in the guard, the definition is significant, but the value is not, so the preprocessor automatically sets the value to 1.
#endif
The "end if" directive ends or closes the #ifndef directive.
The #include guard directives. In conjunction, the three directives conditionally include or exclude code from the source code file. The constant name following the #ifndef and #define directives must be unique throughout the program (i.e., used in only one header file). Traditionally, programmers create unique constant names from the header file name (which programmers derive from the class name) by following two or three steps:
  1. Convert all file-name characters to upper case (a traditional convention for symbolic-constant names)
  2. Prepend an underscore character at the beginning of the name, and replace the "." with an underscore
  3. Some programmers optionally add an underscore at the end of the name
The #include, or macro guard, is a little "bulky" and complicated, but ANSI C and C++ standards include all three directives. So, any ANSI-compliant compiler can correctly process an #include guard.

#pragma once

The #pragma preprocessor directive provides additional information to the compiler system. It introduces a non-standard behavior that pragmatically solves a problem using the unique features of a vendor's hardware or software. The ANSI C and C++ standards don't cover the pragma directive, so it may not be portable (i.e., be easily moved from one computer to another). Like the #include guard, the #pragma once directive ensures that a block of code is included in a source code file exactly once. It is more compact (i.e., has much less code) than a guard and avoids the problem of creating unique identifiers or constant names. Although most contemporary compilers on Windows, macOS, and Linux support #pragma once, doing so is not yet required by the ANSI standard, so it is not guaranteed to work with all compilers.

Robust Header Files

Programmers typically implement C++ classes as two files: a header file and a source code file. The header file contains the class specification, prototypes for large functions, and complete definitions for small functions. The source code file contains static member variables and the definitions for large functions. This organization simplifies writing programs based on a client and supplier model. The application program is the client, while the class, represented by the header and the source files, is the supplier. For the client or application program to use the supplier's services, it's sufficient for the client to #include the supplier's header file. The compiler system compiles and links the supplier's source code file with the application.

Moving the Actor-example classes to separate header files has a significant and easily overlooked consequence. The three classes and the main function of the example use the string and iostream library classes. One set of include directives provides the necessary class specifications to all parts of the single-file version. However, naively copying the classes to new, separate header files can inadvertently create error-prone code.

#include <iostream>
#include <string>
using namespace std;
#include "Person.h"
#include "Actor.h"
#include "Star.h"
#include "Person.h"
#include "Actor.h"
#include "Star.h"
#include <iostream>
#include <string>
using namespace std;
(a)(b)
Robust vs. error-prone header files. Based on the three Actor classes, the code blocks represent two typical ways programmers can include the header files needed by a main function. The blocks contain the same code but in a different order. The using statement must follow the inclusion of the system headers (those whose names are in angle brackets), but its placement is otherwise insignificant. For this example, imagine that we have moved each class specification to a separate header file but without the highlighted code.
  1. This sequence compiles without error because the highlighted code makes the string and iostream class specifications available to the following Actor classes.
  2. This sequence will not compile. The preprocessor reads the code from the top downwards, so the string and iostream classes are unspecified, and the class names are undeclared when the preprocessor processes the Actor classes.
Adding the highlighted code to each Actor class makes the classes more robust and easier for application programmers to use. Robust code is "capable of performing without failure under a wide range of conditions." The robust version of the Actor example is insensitive to the header file inclusion order in the client code.
Our operational rule of thumb - almost our mantra - is, "Whenever we use a class in any file, we #include its header in that file."

Actor2 Header Files

#ifndef _PERSON_H_
#define _PERSON_H_

#include <iostream>
#include <string>
using namespace std;


class Person
{
	. . .
};

#endif
#pragma once

#include <iostream>
#include <string>
using namespace std;


class Person
{
	. . .
};
 
 
 
Actor2: Person.h. There are two alternate ways of ensuring that a class specification is included in a source code file exactly once. The classic guard requires using the same constant name with both the #ifndef and #define directives. The Person class has a member variable and a constructor parameter that are instances of the string class, so the string header file is #included in the Person header file.
#ifndef _ACTOR_H_
#define _ACTOR_H_

#include <iostream>
#include <string>
#include "Person.h"
using namespace std;


class Actor : public Person
{
	. . .
};

#endif
#pragma once

#include <iostream>
#include <string>
#include "Person.h"
using namespace std;


class Actor : public Person
{
	. . .
};
 
 
 
Actor2: Actor.h. The #include guard works here as described in the previous figure. The inheritance relationship, including the Actor constructor, is based on the Person class. That means that the Person class specification precedes the Actor class. Inheritance is a one-way relationship: an Actor "knows about" a Person, but an Actor doesn't "know about" a Star, so the Star header is not needed.

Like the Person class, Actor has a member variable and a constructor parameter that are instances of the string class, so the string header is #included in the Actor header file. Technically, doing this is unnecessary: the Actor header file includes the Person header, which includes the string header file. However, imagine an application program that uses the Actor class but does not directly deal with a Person. Following the best object-oriented programming practice, the Actor class designer should not require application programmers to "know" the implementation details of the Actor class, including that it subclasses Person. (For example, how much do you know about how C++ implements its string class?)

#ifndef _STAR_H_
#define _STAR_H_

#include <iostream>
#include <string>
#include "Actor.h"
using namespace std;


class Star : public Actor
{
	. . .
};

#endif
#pragma once

#include <iostream>
#include <string>
#include "Actor.h"
using namespace std;


class Star : public Actor
{
	. . .
};
 
 
 
Actor2: Star.h. Using the inheritance relationship and the Star and Actor classes, the figure illustrates the similarities between the #include guard and the #pragma once directive. It also demonstrates the appropriate header files to #include in Star.h: string, because the constructor uses it, and Actor to support inheritance. However, Star does not directly depend on Person - it doesn't appear anywhere in the Star class - so the Star header doesn't include the Person header file. Both the #include guard and the pragma directive guarantee that the preprocessor includes the Actor header file exactly once.

Each Actor2 header file includes the C++ string header. The multiple inclusions don't cause errors because the string class employs a guarding mechanism, either an #include guard or a #pragma once. We unknowingly relied on this standard practice when we created our first multi-file program (Time, see figures 2 and 3) and placed the #include <iostream> directive at the beginning of each source code file. This example demonstrates the value of the guarding mechanism: programmers can use existing classes and functions with little regard for their implementation details.

Actor2 Source Code File

#include "Person.h"
#include "Actor.h"
#include "Star.h"


int main()
{
	Person	director("Steven Spielberg");
	director.display();

	Actor	sidekick("Harvey Korman", "Dilbert");
	sidekick.display();

	Star	big_star("John Wayne", "Cranston Snort", 5000000);
	big_star.display();

	return 0;
}
Actor2: driver.cpp. The main function, the only code in driver.cpp, represents an application program that uses the classes specified above. It uses the class names as type specifiers to define the variables director, sidekick, and big_star. It also uses the class names to call the class's constructor functions.

The program demonstrates our operational rule of thumb or mantra: whenever we use a class name, we include the corresponding header file - even if some inclusions are superfluous. Including Person.h and Actor.h is superfluous because Star.h incorporates all three class specifications into the program. Nevertheless, making our classes sufficiently robust so that multiple inclusions don't cause compiler errors makes them easier for application programmers. Following this practice, it's easy to see how a program might include a class specification more than once.

Header Files Summary

Rules of thumb for using header files:

  1. Each class should have a header (i.e., .h) file
  2. If a class has "long" functions (generally more than three or four statements), it should also have a source code (i.e., .cpp) file
  3. Programmers should define long functions and static member variables in the source code file
  4. Prevent "multiple definition" errors stemming from inlined functions by guarding class specifications:
    1. Use an include guard: #ifndef / #define / #endif
    2. Or use #pragma once
  5. Be economical but unambiguous with #include directives - use what you need but need what you use. All program files (header and source code) should include the header files specifying referenced classes and prototyping functions not associated with classes, but they should not include extraneous header files
  6. The compiler system reads and processes each file once, from top to bottom, and every identifier or name the compiler encounters must be declared before it's use