From: carden@sdf.lonestar.org (Jack Carden)
Newsgroups: comp.lang.c++
Subject: Re: Idempotent header file proposal
Summary: alternate proposal
Keywords: alternative ideas header file organization
Date: 10 Dec 92 03:29:29 GMT
Followup-To: comp.lang.c++
Organization: sdf full-shell Public Access Unix - Dallas, TX - 214/436-3281

In article <1992Nov19.090742.23696@cbfsb.cb.att.com> nh@cbnewsg.cb.att.com (nicholas.hounsome) writes:
|One problem with C++ is that when used properly it requires huge
|numbers of header files most of which are included multiple times.
|Of course I know that there is the crude workaround :
|
|#ifndef HEADER_H
|#define HEADER_H
|
|stuff
|
|#endif
|
|But the preprocessor still has to find, open , and read the whole file
|so it does not realy gain you any efficiency.
 
Please allow me to add another idea to this discussion.  While attending
the USENIX C++ Conference, in Denver, 1988, I heard an attendee explain
his method for controlling include file use.  I am sorry that I can't
give proper credit to this person, but I liked his ideas so much that I
adopted them then and have used them since in all the development projects
I have tackled.  I still like this approach better than precompiled header
files (unless I would gain confidence that the preprocessor "understands"
my class relationships.)
 
The approach requires some discipline in header file creation, but I
haven't found this to be a problem.  One begins with the idea of a master
include file for the project or the component to be created.  The master
include file will include all other required files, but only those required
in the particular "module" under compilation.  The master file will know
which files to include because the author has used #define to indicate
required files (generally one class per include file.)
 
The individual class include files will generally look like this:
 
== classA.h ==
#ifndef Pass2                   // test for appropriate "pass" on this file
#define Incl_classB             // indicate the need for classB definitions
#else                           // otherwise proceed with classA definitions
 
class classA {
protected:
      void foo(classB &);
...
};
#endif
 
 
== classB.h ==
#ifndef Pass2
#define Incl_classC
#define Incl_assert  // can also indicate the need for standard include files
#else
 
class classB : public classC {
...
};
#endif
 
The master include file will generally look like this:
 
== master.h ==
// begin first pass of all project-related include files
#ifdef Incl_classA  // inclusion of classA preceeds classB because of dependency
#include "classA.h"
#endif
 
...
 
#ifdef Incl_classB  // inclusion of classB preceeds classC because of dependency
#include "classB.h"
#endif
 
...
 
#ifdef Incl_classC  // inclusion of classC follows classA and classB
#include "classC.h"
B
#endif
 
...
 
// include any "standard" include files as requested
#ifdef Incl_assert
#include <assert.h>
#endif
 
// begin second pass of all project-related include files.
// Note the inverted order of inclusion.
#define Pass2
#ifdef Incl_classC  // inclusion of classC lays the foundation for classA and classB
#include "classC.h"
#endif
 
...
 
#ifdef Incl_classB  // inclusion of classB preceeds classA because of dependency
#include "classB.h"
#endif
 
...
 
#ifdef Incl_classA  // inclusion of classA follows classB because of dependency
#include "classA.h"
#endif
 
All source files will generally look like this:
== classA.C
#define Incl_classA             // indicate the need for classA definitions.
#define Incl_classB             // we can also request any others we want.
                                // we assume that classA may request others.
#define Incl_classD             // we might require other classes as well.
#include "master.h"
 
void classA::
     foo(classB &)
{
...
}
 
This organization seems to provide two benefits:
   1) It forces the organization of include files to be consistent and
      it causes the designer to consider the appropriate order for 
      inclusion in master.h thereby prohibiting circular references.
   2) It limits the pre-processor to two scans of each of the include files
      for each compilation.
 
Are there over-simplifications that I have missed?  This approach has
proven very satisfactory (at least in my view) on three different projects.

Jack Carden                             The eye may see for the hand,
Software Engineering                    but not for the mind.
Carrollton, TX  75006                             <Thoreau, 1849>
Internet: carden@sdf.lonestar.org
Newsgroups: comp.lang.c++