The object doesn't get defined more than once if the programmer (me) follows
the rules:
1. In ordinary source (.c) files wishing access to the declarations, I
simply do:
#include "filename.h"
2. In one and only one source file, I define INSTANSTIATE_OBJECTS before
including the file, and it gives me the declaration and the instantiation:
#define INSTANTIATE_OBJECTS
#include "filename.h"
This is -exactly- equivalent to just putting the object declarations
in the header and putting the definition in one of the source files
(the source file you #define INSTANTIATE_OBJECTS in). Except doing it
with precompiler macros is slightly more work than you need to be
doing, and also requires you to explain what you are doing to other
people reading your code (whereas declaring objects as extern,
possibly with a comment like "// defined in file.cpp", is clear and
concise).
I've used this technique many times on simple uninitialized variables to
avoid the hassle of maintaining two different source files (the .h and .c)
with the same object names. A typical xyz.h file would contain something
like this:
It depends on how you look at it I guess. If you are going by "number
of files" then I guess having a .h file is "better" than having both a
source and a header. If you are going by "lines of code", you are
probably typing more with your preprocessor stuff. Also in either
case, you still have the dependency on a given source file. With your
method, if you remove the source file that #defines
INSTANTIATE_OBJECTS, you have to put that #define somewhere else. With
the other method, you still have to compile the source file with the
definitions into the project -- but at least it's clear that
"globals.cpp" matches "globals.h", rather than you saying "well if
something_unrelated.cpp isn't compiled then the objects declared in
globals.h won't be defined unless you move such and such preprocessor
definition to another arbitrary source file".
Both C and C++ have constructs and mechanisms ("extern", and the
linker) to cleanly and clearly do -precisely- what you are doing with
precompiler macros.
#if defined( INSTANTIATE_XYZ_OBJECTS )
#define EXTERN
#else
#define EXTERN extern
#endif
EXTERN int xyz1;
EXTERN int xyz2;
That single block of source code serves both the purpose of declaration and
instantiation of xyz1 and xyz2, depending on whether the including .c file
first declares INSTANTIATE_XYZ_OBJECTS.
Until somebody sees that you only named it "EXTERN", and starts using
EXTERN as a substitution for the real extern, expecting it to behave
sanely. If you insist on doing it this way instead of just defining
the objects in another source file, at least pick a better macro name
than EXTERN.
But this technique only works for
uninitialized variables. I'm trying to find a similar technique for
initialized variables.
You do it like this:
header.h:
extern int xyz1;
extern int xyz2;
one_source_file.cpp:
int xyz1 = 400;
int xyz2 = 234;
Again, if you insist on doing it with precompiler macros, you would do
this:
extern int xyz1;
extern int xyz2;
#ifdef INSTANTIATE_XYZ_OBJECTS
int xyz1 = 400;
int xyz2 = 234;
#endif
Having the declaration and definition in the same source file won't
cause any problems, there is no need to put the "extern int"
declarations in an #else block. But again, since you are only
#defining INSTANTIATE_XYZ_OBJECTS in a single source file before
#including that header, you don't have to bother with the preprocessor
at all -- just put the definitions right in that source file.
Editorial... It is a pet peeve of mine that the C and C++ languages require
me to create two source code lines (one in a .c file, and one in a .h file)
associated with the same object name. Simply put, one source code line is
easier to maintain than two source code lines, especially when those two
source code lines must, by edict of C/C++, reside in separate files. That's
edict is a maintenance hassle that I would gladly circumvent if I knew how.
The reason for this is that you need to have the declaration of an
object available so that the compiler knows it exists, and thus knows
the type of it and can compile your code. However, the object itself
is only defined in one translation unit. Declaration and definition
are two separate concepts. There is no other alternative. Let's say
that "declarations" didn't exist and you had to define everything
everywhere. What happens if you put:
int i = 3;
In a header, and that serves as both declaration and definition? Then
you compile multiple source files that use it. Then you change it to:
int i = 4;
And recompile only some of the source files. Or if it is a variable,
such as errno, that is defined in a library elsewhere (where you have
no control over the initialization of it -- yet you would still be
"initializing" it to a certain value in errno.h). A situation like
that would become a maintenance nightmare.
You are already running into a situation where you have to maintain a
separate declaration and definition for variables (you have to do this
with functions, too -- you have to have *some* way of saying "this
function exists" without repeating the code for it in every header you
use it in). As you can see, it actually *is* useful to have separate
declarations and definitions -- this completely solves your problem of
wanting to use initialized data in multiple source files. You may want
to consider removing that pet peeve from your list of pet peeves!
So what it comes down to is: use the language features that are
already in place for doing exactly what you are trying to do. Declare
variables, as extern, in headers, define them in a single source file,
and don't use the preprocessor to emulate a feature that the language
already has.
Jason