You are here:  OakRoadSystems > Programming > C++ Practices > 6/Builds
 
  Home
  Site Map
  Shareware Utils
  Articles
      Programming
      Internet
      Mathematics
      General
  Search

6. System Building

revised 15 Feb 2001
Copyright © 1998-2002 Stan Brown, Oak Road Systems

 

My own C++ code has to fit into a project. This page talks about practices that should make it fit better.


 

My code will compile without errors or warnings.

Rationale
It's safer. Otherwise, if I get in the habit of ignoring particular warnings "because they don't matter", then I risk one day ignoring a warning that does matter.
 
Details
[UPDATED! 2000-03-03 -- to next update]
(Moved Microsoft-specific material to a separate page and clarified and expanded it considerably.)

My practice has always been to compile with the highest possible warning level, and to treat all warnings as errors. Different compilers have different options to indicate this.

Unfortunately, one of the leading implementations makes this difficult to impossible. If you're using MSVC++, you may want to have a look at Microsoft vs. C++ for some details on the problems and what limited measures I've evolved to deal with them.


 

I'll use a standard guard for header files.

Details
C++ doesn't like it when you redefine something, even if the redefinition matches the original definition. To guard against multiple inclusion, I'll begin file gronk.h with
    #ifndef GRONK_H_
    #define GRONK_H_
and end it with
    #endif // GRONK_H_
[UPDATED! 2000-03-03 -- to next update]

Some people like to use a leading underscore, but the standard says that user programs may not create identifiers that begin with an underscore followed by a capital letter, on pain of undefined behavior of the generated code.

[NEW! 2000-03-03 -- to next update]

The above internal include guards are pretty much necessary for safety. But in February 2000 there was an interesting discussion about external include guards on comp.lang.c++.moderated. An external guard puts the #ifndef-#endif pair outside the #include, like this:

    #ifndef GRONK_H_
    #include "gronk.h
    #define GRONK_H_
    #endif
As you see, the #define follows the #include, so there's no interference with an internal include guard. The potential benefit of external guards is that the compiler never even has to open the include file a second time, and on some compilers this significantly speeds up build times.

So internal guards are a necessary safety measure, and external guards might speed things up. Why not use both? Well, some compilers don't build significantly faster with external guards; and external guards do add a maintenance burden, however slight. The strongest reason is that some compilers already recognize and skip a reused header file, and in the future probably more will do so. At present, whether to use external guards depends on the compiler, the environment, and the size of the project.


 

I'll define only one class per file.

Exceptions
[UPDATED! 2000-06-16 -- to next update]
The intent of this statement is that each file will declare only one class intended for use by client code. Additional classes used only by the main class may go in the same header file.

 

I'll use minimal #includes in header files.

Details
Specifically, I'll include only the header files that are necessary to compile the class definition. Any that are needed only by the implementation can be included in the class implementation file.

Forward declarations can also help to reduce header-file nesting.
 

Rationale
The more nested includes, the longer it takes to compile each file, and the greater the number of files that will have to be recompiled when a given header file changes. Though that's inevitable in any project above the toy level, it's much kinder to users to minimize that as far as possible.

 

I'll use forward declarations to minimize header-file nesting.

Details
If class A contains only pointers and references to type B, then in the definition of A (in file a.h) I can forward declare
    class B;
instead of
    #include "b.h"
This means that when b.h changes, I won't have to recompile everything that includes a.h, because a.h no longer includes b.h. The forward declaration is also sufficient for functions that return a B or take a B passed by value:
    class B;
    B normalized(B rawEntry);     // compiles just fine
[UPDATED! 2000-06-16 -- to next update]
You may be surprised that this example is valid; I was. But it is. Meyers 1997 says "Note that you never need a class definition to declare a function using that class, not even if the function passes or returns the class type by value." To this I would add that, to compile the function declaration, the compiler doesn't need to know anything about B other than that it's a type. The compiler doesn't need a full definition of B until the function is called, because only then does it need to know even how big a B-type object is.
 
See also
Meyers 1997 pages 147 ff.; Buchheit 1994 page 19; Jaffe 1994 page 8

previous   contents     C++ Practices
 contact info  |  site map
Valid HTML 4.0! this page: http://oakroadsystems.com/codeprac/build.htm