How to resolve 'Cannot create pre-compiled header:
initialized data in header' compile-time problems.
by Curtis Krauskopf
Pre-compiled headers are one of the most important
compiler features for reducing compilation times. Pre-compiled
headers allow the compiler to reduce the project's overall
compile time because information is reused from previous
compilations.
Know the Rules
Here are some rules to keep in mind when using precompiled
headers:
- Use precompiled headers on compilation units that
are relatively large, relatively complicated or have
many external dependencies. The compiler's bookkeeping
associated with precompiled headers takes some time
too. For simple units, the bookkeeping overhead outweighs
the benefits of precompiled headers. When used judiciously,
the time saved by using a precompiled header will
vastly outweigh the compiler's bookkeeping overhead.
- Precompille headers that are maturely developed
and are not changed frequently. The bookkeeping overhead
of frequently recreating a precompiled header might
outweigh any gains from using the precompiled header.
- Limit the number of precompiled header units that
are in a precompiled header. Although it might be
tempting to create a precompiled header that includes
all of C libraries, and all of the C++ libraries and
all of the STL libraries and even the Boost libraries
(just in case any of them are used), you'll discover
that the disk I/O associated with the enormous size
of the precompiled header is a boat anchor that drives
down the precompiled header's benefits. Keep it simple.
Limit the precompiled header to only the header units
that you're actually using in the project.
- Properly guard every header so that it is only compiled
one time. This means using a #ifdef and a unique #define
in every header module. A template example of this
for a file called MyModule.h is:
#ifndef MyModule_H
#define MyModule_H
...
... // insert the contents of the header file here
...
#endif
- Limit the precompiled header units only to units
that are frequently used across multiple compilation
units.
- Do not put data in precompiled header units. Fortunately,
the compiler will generate a warning message W8058
(a BCC32 warning). Here is an example:
[BCC32 Warning] somefile.hpp(10): W8058 Cannot pre-compile
header: initialized data in header
This warning says that line 10 of somefile.hpp contains
code that uses data.
The last rule is sometimes the most difficult one to
obey but at least the compiler will warn us when we
break the rule.
String Example
This article will use the simplest
possible example: a main module that uses a single
function in a static library. To make it clear which
module is which, the main module will be called main.cpp
and the library is called library.cpp
(pretty original, huh?). The library has one header
file called library.h.
Both the library.cpp and
the main.cpp include the
library.h file.
An example of data in a header file is in Listing 1.
Library.h: |
#ifndef Library_H
#define Library_H
#include <string>
bool hasWhiteSpace(const std::string &str, const std::string &whitespace = " \n\t");
#endif |
The hasWhiteSpace function
in the library allows the programmer to pass a string
to it and check if the string has any of the specified
whitespace characters. For the programmer's convenience,
the function has a default parameter that defines several
common whitespace characters. However, the programmer
can always choose their own set of whitespace characters
and the hasWhiteSpace
function will analyze the string with those specific
characters.
The code in Listing 2 is an
example of calling the hasWhiteSpace
function from main.cpp:
In the example, the first call to hasWhiteSpace
uses the default parameter to check if the value in
the userInput string has
a blank, a newline character or a tab character.
The next call to hasWhiteSpace
in the example checks specifically if the userInput
string has a blank character.
Fixing the W8058 warning
There are several techniques for fixing the W8058 warning:
- Overload the function
- Make the value a constant
- Use an empty string
Let's look at an example of each solution.
Overload the function
Overloading the function has the effect of removing
the default parameter. One overloaded function is needed
for each default parameter plus one overloaded function
is needed for all of the parameters. For example, a
function with 3 default parameters needs 4 overloaded
functions (one for each default parameter plus the original
function with all of the parameters). Table
1 shows an example of the overloaded functions.
Caveat: the default parameters in Table
1 do not cause a W8058 warning. I'm showing a simple
example of how a function with default parameters can
be recoded to be overloaded and provide the same functionality.
The example for hasWhiteSpace
in Listing 1 has only one default
parameter, so two overloaded functions are in Listing
3.
The implementation for the first overloaded function
(hasWhiteSpace(str)) is
in Listing 4.
The trick for removing the initialized data from the
header is to overload the function and put the initialized
data in the .cpp file. The overloaded functions provide
the same interface that the original function using
default parameters provided. The interface in the .h
file no longer has an initialized data dependency.
The down-side to this solution is that it makes maintenance
a little more difficult because when the maintenance
programmer (you?) needs to change the value of a default
parameter in an overloaded function, you'll have to
make the same change in all of the other overloaded
functions. This is the kind of thing that can easily
be overlooked and then you'll wonder why some function
calls are using the new behavior and some function calls
are using the old behavior.
If you don't mind the relatively minor runtime overhead,
you could chain the implementations so that they progressively
call the next overloaded function. For example, going
back to the four overloaded functions in Table
1, the implementation would look like Listing
5.
Another down-side to the "overloading the functions"
solution is that it creates a plethora of overloaded
functions. Although I personally don't have any problem
with lots of overloaded functions, I've had colleagues
tell me that it makes the interface more difficult to
understand. Because I respect their opinions, I try
to limit the number of overloaded functions I have in
the libraries I write.
Make the value a constant
Another way to bypass the W8058 warning is to make
the default parameter an externally defined constant.
Listing 6 shows an example
of the hasWhiteSpace function
with an externally defined constant string. Listing
7 shows the implementation of the hasWhiteSpace
function in the Library.cpp
file. The Const_DefaultWhitespace
symbol is defined in the .cpp file.
To me, this seems to be an improvement over the multiple-overload
solution because the default parameters are still defined
in one place and because there isn't an explosion of
overloaded functions cluttering the .h file.
Use an Empty String
This solution has three prerequisites:
- The compiler is a Borland compiler
- Borland's VCL can be compiled into the main module
- An empty string in the default parameter would not
normally be a valid value for the function.
Default empty strings do not exist in the STL. For
Borland compilers, however, the VCL offers three types
of empty string constants:
- System::UnicodeString EmptyStr;
- System::WideString EmptyWideStr;
- System::AnsiString EmptyAnsiStr;
All three of these strings are defined in the SysUtils.hpp
header (which is automatically included when #including
<vcl.h>).
By refactoring the second parameter of hasWhiteSpace
from a std::string to an
AnsiString, an EmptyAnsiStr
can be used as a default parameter. This works because
EmptyAnsiStr is defined
as an extern in SysUtils.hpp.
It also works because an empty string would not normally
be a valid value in the second parameter of the hasWhiteSpace
function.
Putting this all together, Listing
8 has the solution for the header file and Listing
9 has the solution for the .cpp file.
The Library.cpp contains
two implementations of hasWhiteSpace.
One takes an an AnsiString
as the second parameter and the other takes a std::string
as the second parameter.
The careful reader will want to cry "Shenanigans"
because this solution is really no different than the
first solution -- both overload the function.
While that is true, the overloading would not have
been needed if the original string had been one of the
VCL variants: UnicodeString,
WideString or AnsiString.
In that case, the Library.h
file would only have been changed to replace the default
parameter with the appropriate SysUtils.hpp-defined
symbol: either EmptyStr,
EmptyWideStr or EmptyAnsiStr.
The hasWhiteSpace function
in Library.cpp file would
still have been modified to detect if an empty string
had been passed to it.
Unfortunately, this solution only moves the problem
to another module because CB2010 complains that Dialogs.hpp
also has initialized data in the header:
[BCC32 Warning] Dialogs.hpp(1296): W8058
Cannot create pre-compiled header: initialized data
in header
This is a problem in the Dialogs.hpp file that Embarcadero
has known about for a long time but for some reason
has not been fixed yet.
Int Example
Sometimes data in a header looks like this:
static unsigned sym = 127;
The solution for this problem is to add 'const'
to the declaration. const
can be on either side of the static
keyword:
static const unsigned sym = 127; // okay
or
const static unsigned sym = 127; // okay
Conclusion
Precompiled headers reduce the compile-time by allowing
the compiler to cache its work for future compilations.
One of the main restrictions for using precompiled
headers is that data can not be initialized in any precompiled
header.
Fortunately, there are four different techniques for
solving this problem: overloading the function, making
the default parameter a constant, using an empty string
constant or making the variable declaration const.
Copyright 2003-2010 The Database Managers, Inc.
Popular C++ topics at The Database Managers:
|