Large scale C++ software design,

P

puzzlecracker

Large scale C++ software design, by John Lakos - people any good
recommendations to this book?
 
E

E. Robert Tisdale

puzzlecracker said:
Large scale C++ software design, by John Lakos
- people any good recommendations to this book?

This book was published in 1996
and contains a lot of obsolete advice.
For example, Chapter 2 Ground Rules,
Section 5 Redundant Include Guards, page 85,

Minor Design Rule
Place a redundant (external) include guard
around each preprocessor include directive
in every header file.

Today, we expect the C preprocessor to remember
idempotent header files and read them only once.
 
P

puzzlecracker

E. Robert Tisdale said:
This book was published in 1996
and contains a lot of obsolete advice.
For example, Chapter 2 Ground Rules,
Section 5 Redundant Include Guards, page 85,

Minor Design Rule
Place a redundant (external) include guard
around each preprocessor include directive
in every header file.

Today, we expect the C preprocessor to remember
idempotent header files and read them only once.
what would you supplement this book with? Any viable alternatives or
construction suggestions that would go on par with this book?
 
D

davidrubin

I would personally recommend Meyers, Dewhurst, and various books by
Sutter. However, none of these really address physical design in the
way that Lakos does.
 
S

Shezan Baig

E. Robert Tisdale said:
This book was published in 1996
and contains a lot of obsolete advice.
For example, Chapter 2 Ground Rules,
Section 5 Redundant Include Guards, page 85,

Minor Design Rule
Place a redundant (external) include guard
around each preprocessor include directive
in every header file.

Today, we expect the C preprocessor to remember
idempotent header files and read them only once.

What are you talking about? Is that specified in standard C++? Does
the following code compile for you?

blah.h
------
int somefunction() { return 9; }

blah.cpp
--------
#include <iostream>
#include "blah.h"
#include "blah.h"

int main() { std::cout << somefunction() << std::endl; }

I tried it on gpp and got "error: redefinition of 'int
somefunction()'". If it compiles for you, I'd really like to know what
compiler you're using.

Thanks,
-shez-
 
V

Victor Bazarov

Shezan Baig said:
What are you talking about?

He is talking about advice John Lakos gave in his book.
Is that specified in standard C++?

Why should that matter? The Standard doesn't specify any
design guidelines nor does it say what features of a C++
compiler make a better competitor on today's market. If
the preprocessor remembers what headers it has already
included, the redundant guards become unnecessary.
Does
the following code compile for you?

blah.h
------
int somefunction() { return 9; }

blah.cpp
--------
#include <iostream>
#include "blah.h"
#include "blah.h"

int main() { std::cout << somefunction() << std::endl; }

I tried it on gpp and got "error: redefinition of 'int
somefunction()'". If it compiles for you, I'd really like to know what
compiler you're using.

No, what you presented here will certainly not compile.
However, if you add

#pragma once

to 'blah.h', some modern compilers will not preprocess it
again. The existence of 'once' pragma makes *external*
include guards unnecessary.

V
 
D

davidrubin

These small details are less important compared to the overall
methodology presented in the book. The main point is to recognize that
physical design is a separate problem from logical design, and then to
learn how to implement a physical design.
 
E

E. Robert Tisdale

Shezan said:
What are you talking about?
Is that specified in standard C++?
Does the following code compile for you?

blah.h
------
int somefunction() { return 9; }

blah.cpp
--------
#include <iostream>
#include "blah.h"
#include "blah.h"

int main() { std::cout << somefunction() << std::endl; }

I tried it on gpp
and got "error: redefinition of 'int somefunction()'".
If it compiles for you,
I'd really like to know what compiler you're using.

You are confused.
John Lakos might write:
cat blah.h
#ifndef GUARD_BLAH_H // *internal* include guard
#define GUARD_BLAH_H 1
int somefunction() { return 9; }
#endif//GUARD_BLAH_H 1
cat blah.cpp
#include <iostream>
#ifndef GUARD_BLAH_H // *external* include guard
#include "blah.h"
#endif//GUARD_BLAH_H
#ifndef GUARD_BLAH_H // *external* include guard
#include "blah.h"
#endif//GUARD_BLAH_H

int main() { std::cout << somefunction() << std::endl; }

The redundant external include guards are not necessary
because, after the C preprocessor reads blah.h once,
it remembers that blah.h is idempotent
and it won't even attempt to read it a second time.
 
S

Shezan Baig

Victor said:
However, if you add

#pragma once

to 'blah.h', some modern compilers will not preprocess it
again. The existence of 'once' pragma makes *external*
include guards unnecessary.

That worked! Cool, thanks!

-shez-
 
I

Ioannis Vranos

Victor said:
No, what you presented here will certainly not compile.
However, if you add

#pragma once

to 'blah.h', some modern compilers will not preprocess it
again. The existence of 'once' pragma makes *external*
include guards unnecessary.


Still it can not be considered portable, and I wonder why at least the
2003 standard did not include this directive.
 
D

Dietmar Kuehl

puzzlecracker said:
Large scale C++ software design, by John Lakos - people any good
recommendations to this book?

The book is somewhat aged and predates modern C++ techniques
like template meta programming which are neither covered nor
can be addressed with the techniques in this book (partially
because compiler writers refuse to implement the complete
standard, in particular exporting templates). If you keep
this in mind, Lakos' book is excellent reading.

The discussion of the [redundant] external include guards
brought up in thread may be outdata after nearly 10 more years
but it is only one of many techniques Lakos uses to keep huge
project managable. Many of the other techniques have impacts
on the actual C++ code as well as on object oriented design
for achieving independent units.
 
R

Richard Herring

E. Robert Tisdale said:
John Lakos might write:

#ifndef GUARD_BLAH_H // *internal* include guard
#define GUARD_BLAH_H 1
int somefunction() { return 9; }
#endif//GUARD_BLAH_H 1

#include <iostream>
#ifndef GUARD_BLAH_H // *external* include guard
#include "blah.h"
#endif//GUARD_BLAH_H
#ifndef GUARD_BLAH_H // *external* include guard
#include "blah.h"
#endif//GUARD_BLAH_H

int main() { std::cout << somefunction() << std::endl; }

The redundant external include guards are not necessary
because, after the C preprocessor reads blah.h once,
it remembers that blah.h is idempotent
and it won't even attempt to read it a second time.

The C++ preprocessor has no notion of idempotency. (I have no idea
whether the C preprocessor does or does not, and it's off-topic here
anyway.) All it "remembers" is that the include-guard macro has been
defined.

If you mean "include guards should go inside the included file, not
outside", just say so.
 
D

davidrubin

No, redundant guards go in the .h file only, not in the .cpp file. The
reason has more to do with reducing compile time than providing some
kind of protection against multiply defined symbols. /david
 
V

Victor Bazarov

Ioannis said:
Still it can not be considered portable, and I wonder why at least the
2003 standard did not include this directive.

Pragmas are by definition implementation-defined.

There are two sides to the issue: following language rules and good
large-scale system design. They are orthogonal. If some compiler
features that aren't in the language proper allow you to achieve
a better result in the system design, who is to tell you not to use
it because "it can not be considered portable"? Who cares that it
is not portable if it does what it intends to do?

IOW, don't get hung up on portability.

V
 
D

Dietmar Kuehl

Mike said:
Well, then, they're not *redundant*, now, are they?

They are: the idea is that you enclose each '#include' statement
in a header file with the same guards as the header file uses
internally. That is, if you omit the redundant guards, there will
be no semantic difference. However, the compiler does not need to
search and open the included header file if it was included before
due to the redundant guards.

For example:

/**/ // file: foo.h
/**/ #if !defined(FOO_H) // non-redundant guard
/**/ #define FOO_H
/**/ ...
/**/ #endif

/**/ // file: bar.h
/**/ #if !defined(BAR_H) // non-redundant guard
/**/ #define BAR_H
/**/ # if !defined(FOO_H) // <--- redundant guard!
/**/ # include "foo.h"
/**/ # endif
/**/ #endif

Now, is this an important technique? Probably not today: Some
compiler added a non-portable #pragma to tell the compiler about
headers which only need to be included once (you should, however,
still provide the non-redundant guards anyway to avoid portability
problems; on the other hand, adding these guards can be done
automatically quite easy). Other compilers, e.g. gcc, detect that
the header has include guards and include it once anyway which is,
BTW, one of the reasons why there is no intention whatsoever to
bless the '#pragma once' thing by the standard.

At the time of Lakos' writing the optimizations techniques were no
widespread and include hierarchies often nested quite deep. Combine
this with slower processing (the preprocessor needs to read the
whole file) and [slower] network file systems and you will see the
impact of this technique. However, he goes actually much further
and things become much more interesting: he shows how to organize
the code to make inclue statements obsolete without changing any
function or class just by taking advantage of the knowledge about
things really necessary for declarations to work. ... and he goes
beyond this to decouple classes even further making even more of
the preprocessor hacking unnecessary. The impact of the refactoring
(if you don't do it right from the start of the project) is
tremendous and goes far beyond mere compile-time (although that
really matters; around the time he released his book I worked in a
project where a single built of the system took about 16 hours):
you can change things much more freely if you decouple them
religously (actually, in the same project mentioned above, a change
to essentially an arbitrary class caused the need to recompile
everything, at least from make's perspective; guess how successful
that project was...).

If you are working on a bigger piece of software, Lakos' is still
a must-read, IMO. It may be irrelevant to average toy programs but
it is still relevant for large-scale projects! ... and, to some
extend, even to projects in a different language than C++ (which
also sheds some light on the importance of include guard thingy).
 
M

Mike Smith

Dietmar said:
They are: the idea is that you enclose each '#include' statement
in a header file with the same guards as the header file uses
internally. That is, if you omit the redundant guards, there will
be no semantic difference.

And once you omit the redundant guards, then they can no longer be
*redundant*. The ones in the .cpp file are the redundant ones; once you
remove them, you're left with the ones in the .h file, which are *not*
redundant.
 
S

Shezan Baig

Mike said:
And once you omit the redundant guards, then they can no longer be
*redundant*. The ones in the .cpp file are the redundant ones; once you
remove them, you're left with the ones in the .h file, which are *not*
redundant.

You don't put redundant guards in your .cpp files. You put them in
header files (when you include other header files).

Hope this helps,
-shez-
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top