© Mikael Olsson 2018
Mikael OlssonC++17 Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-3600-0_29

29. Headers

Mikael Olsson1 
(1)
Hammarland, Finland
 

When a project grows, it is common to split the code up into different source files. When this happens the interface and implementation are generally separated. The interface is placed in a header file, which commonly has the same name as the source file and an .h file extension. This header file contains forward declarations for the source file entities that need to be accessible to other compilation units in the project. A compilation unit consists of a source file (.cpp) and any included header files (.h or .hpp).

Why Use Headers

C++ requires everything to be declared before it can be used. It is not enough to simply compile the source files in the same project. For example, if a function is placed in MyFunc.cpp, and a second file named MyApp.cpp in the same project tries to call it, the compiler will report that it cannot find the function .

// MyFunc.cpp
void myFunc() {}
// MyApp.cpp
int main()
{
  myFunc(); // error: myFunc identifier not found
}

To make this work, the function’s prototype has to be included in MyApp.cpp .

// MyApp.cpp
void myFunc(); // prototype
int main()
{
  myFunc(); // ok
}

Using Headers

This can be made more convenient if the prototype is placed in a header file named MyFunc.h and this header is included in MyApp.cpp through the use of the #include directive . This way if any changes are made to MyFunc, there is no need to update the prototypes in MyApp.cpp. Furthermore, any source file that wants to use the shared code in MyFunc can just include this one header.

// MyFunc.h
void myFunc(); // prototype
// MyApp.cpp
#include "MyFunc.h"

What to Include in Headers

As far as the compiler is concerned, there is no difference between a header file and a source file. The distinction is only conceptual. The key idea is that the header should contain the interface of the implementation file—that is, the code that other source files will need to use. This may include shared constants, macros, and type aliases .

// MyApp.h - Interface
#define DEBUG 0
const double E = 2.72;
typedef unsigned long ulong;

As already mentioned, the header can contain prototypes of the shared functions defined in the source file.

void myFunc(); // prototype

Additionally, shared classes are typically specified in the header, while their methods are implemented in the source file.

// MyApp.h
class MyClass
{
 public:
  void myMethod();
};
// MyApp.cpp
void MyClass::myMethod() {}

As with functions, it is necessary to forward declare global variables before they can be referenced in a compilation unit outside the one containing their definition. This is done by placing the shared variable in the header and marking it with the keyword extern. This keyword indicates that the variable is initialized in another compilation unit. Functions are extern by default, so function prototypes do not need to include this specifier. Keep in mind that global variables and functions may be declared externally multiple times in a program, but they may be defined only once.

// MyApp.h
extern int myGlobal;
// MyApp.cpp
int myGlobal = 0;

It should be noted that the use of shared global variables is discouraged. This is because the larger a program becomes, the more difficult it is to keep track of which functions access and modify these variables. The preferred method is to instead pass variables to functions only as needed, in order to minimize the scope of those variables.

The header should not include any executable statements, with two exceptions. First, if a shared class method or global function is declared as inline, that function must be defined in the header. Otherwise, calling the inline function from another source file will give an unresolved external error. Note that the inline modifier suppresses the single definition rule that normally applies to code entities.

// MyApp.h
inline void inlineFunc() {}
class MyClass
{
 public:
  void inlineMethod() {}
};

The second exception is shared templates. When encountering a template instantiation, the compiler needs to have access to the implementation of that template, in order to create an instance of it with the type arguments filled in. The declaration and implementation of templates are therefore generally put into the header file all together.

// MyApp.h
template<class T>
class MyTemp { /* ... */ };
// MyApp.cpp
MyTemp<int> o;

Instantiating a template with the same type in many compilation units leads to significant redundant work done by the compiler and linker. To prevent this, C++11 introduced extern template declarations. A template instantiation marked as extern signals to the compiler not to instantiate the template in this compilation unit.

// MyApp.cpp
MyTemp<int> b; // instantiation is done here
// MyFunc.cpp
extern MyTemp<int> a; // suppress redundant instantiation

If a header requires other headers, it is common to include those files as well, to make the header stand alone. This ensures that everything needed is included in the correct order, solving potential dependency problems for every source file that requires the header.

// MyApp.h
#include <cstddef.h> // include size_t
void mySize(std::size_t);

Note that since headers mainly contain declarations, any extra headers included should not affect the size of the program, although they may slow down compilation.

Inline Variables

As of C++17, variables may be specified as inline, in addition to functions and methods. This allows constant and static variables to be defined in a header file, because the inline modifier removes the single definition rule that would normally prevent this. Once an inline variable has been defined, all compilation units referencing that header will use the same definition.

struct MyStruct
{
  static const int a;
  inline static const int b = 10; // alternative
};
inline int const MyStruct::a = 10;

The constexpr keyword implies inline, so a variable declared as constexpr may also be initialized in a header file. However, such a variable must be initialized to a compile-time constant.

struct MyStruct {
  static constexpr int a = 10;
};

An inline variable is not restricted to only constant expressions, as seen in the following example where the inline variable is initialized to a random value between 1-6. This value is guaranteed to be the same for all compilation units using this header, even though the value is not set until runtime .

#include <cstdlib> // rand, srand
#include <ctime> // time
struct MyStruct {
  static const int die;
};
inline const int MyStruct::die =
  (srand((unsigned)time(0)), rand()%6+1); // 1-6

Note the use of the comma operator here, which evaluates the left expression first and then evaluates and returns the right expression. The left expression uses the current time to seed the random number generator with the srand function. The right expression retrieves a random integer with the rand function and formats the integer into the 1-6 range.

Include Guards

An important point to bear in mind when using header files is that a shared code entity may only be defined once. Consequently, including the same header file more than once will likely result in compilation errors. The standard way to prevent this is to use a so-called include guard . An include guard is created by enclosing the start of the header in an #ifndef section that checks for a macro specific to that header file. Only when the macro is not defined is the file included. The macro is then defined, which effectively prevents the file from being included again.

// MyApp.h
#ifndef MYAPP_H
#define MYAPP_H
// ...
#endif // MYAPP_H

It may also be a good idea to check if the header exists before including it. For this purpose, C++17 added the __has_include preprocessor expression , which evaluates to true if the header file is found.

#if __has_include("myapp.h")
#include("myapp.h")
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.214.6