2021-01-31

18: The Intent and the Usage of Header Files in C++

<The previous article in this series | The table of contents of this series | The next article in this series>

Why does C++ require header files, which may seem to be annoying to create and maintain, while Java, etc. do not? How should I use them, if I should?

Topics


About: C++

The table of contents of this article


Starting Context


  • The reader has a basic knowledge on C++.

Target Context


  • The reader will know why header files are used in C++ and what are to be or not to be put into header files.

Orientation


The complication concerning 'template's is argued in an article.

The new (arguably) right way to define any class static integral field is introduced in an article.

A strategy that puts the definitions of all the static fields of all the classes into a single source file will be introduced in a future article.


Main Body

Stage Direction
Hypothesizer 7 soliloquies.


1: Is Not Creating and Maintaining Header Files a Nuisance?


Hypothesizer 7
C++ is not the most labor-saving programming language.

An annoyance I feel when I define (in fact, 'declare', to be more accurate, as will be discussed later) a class is that I have to create 2 files, the header file and the source file, like these.

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/ClassA.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_ClassA_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_ClassA_hpp__
	
	#include <iostream>
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace usingHeaderFilesTest1 {
				class ClassA {
					private:
						static string s_string;
						string i_string {"another string"};
					public:
						static string getStatically ();
						static string getStaticallyInLine () {
							return s_string;
						}
						ClassA ();
						virtual ~ClassA ();
						ClassA (ClassA const & a_copiedObject);
						virtual ClassA & operator = (ClassA const & a_assignedFromObject);
						virtual string getInstanceWise ();
						virtual string getInstanceWiseInLine () {
							return i_string;
						}
						friend ostream & operator << (ostream & a_outputStream, ClassA const & a_classA);
				};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/ClassA.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/ClassA.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace usingHeaderFilesTest1 {
			string ClassA::s_string {"a string"};
			
			string ClassA::getStatically () {
				return s_string;
			}
			
			ClassA::ClassA () {
			}
			
			ClassA::~ClassA () {
			}
			
			ClassA::ClassA (ClassA const & a_copiedObject): i_string (a_copiedObject.i_string) {
			}
			
			ClassA & ClassA::operator = (ClassA const & a_assignedFromObject) {
				i_string = a_assignedFromObject.i_string;
				return *this;
			}
			
			string ClassA::getInstanceWise () {
				return i_string;
			}
			
			ostream & operator << (ostream & a_outputStream, ClassA const & a_classA) {
				a_outputStream << a_classA.i_string;
				return a_outputStream;
			}
		}
	}
}

Certainly, some methods could be made 'inline' (which I do not do wantonly), but at least, static fields require the source file (although I usually put the definitions of all the static fields (but not static methods) of all the classes into a single source file in order to control the order of initializations).

Is it not a nuisance? I not only have to create 2 files, but also I have to write the '#include' directive, write again the name space part, "namespace theBiasPlanet {~", and write again the method signatures like "string ClassA::getStatically ()". And when I modify the class, I have to open the 2 files, and modify them to preserve the consistency.

Well, an IDE may alleviate the pain somehow, but I do not use any IDE, because of some reasons (heaviness, forced-to-use unsatisfactory text editors, uncontrollable automatically generated code, vendor lock-in, etc.).


2: Why Is Any Header File Required in C++, or Is It?


Hypothesizer 7
Is that because C++ is a primitive programming language? . . . Well, I do not completely deny that aspect (C++ is an annex onto C, instead of being a programming language newly designed without any fetter, and old, having the obligation to preserve the compatibility to the past (not necessarily good) decisions), but there is a specific reason why C++ uses header files: C++ performs (completely) separate compiling.

Also Java does separate compiling, as 'javac' can take a single source file? . . . No, it does not: 'javac' can not compile any single source file separately, but has to look into the class files that the source file depends on, after compiling the necessary source files if some of the class files have not been created yet.

For example, when 2 public class source files, 'ClassA.java' and 'ClassB.java', are written and 'ClassA' depends on 'ClassB', 'ClassA.java' cannot be compiled without 'ClassB.java' being compiled together.

When any C++ source file is compiled, not any other source file or any library is looked into, which is 'separate compiling'.

Why does that require header files? Well, it does not necessarily require header files, but at least, it requires some information fed into the source file.

For example, if the source file calls a function that is defined in another source file, at least, the signature of the function has to be fed into the compiled source file.

Such information could be, in fact, directly written into the compiled source file, but writing the same information directly again and again into multiple source files would be inefficient and mistake-prone, so we usually write the information only once into a header file and include the header file into multiple source files.

So, header files are a technique for labor-saving and mistake-preventing, not malice to annoy programmers, which has originated with a necessity from the C++ characteristic that it performs separate compiling.

Well, there may be a question that whether separate compiling itself is really a good idea nowadays, but I cannot answer the question.


3: What '#include' Does


Hypothesizer 7
It will be beneficial to be aware that '#include' is a preprocessor directive.

That is, the directive inserts the contents of the header file into the source file BEFORE the proper compiler is invoked: the compiler does not look into the header file, but looks into only the preprocessed source file.

Let me remind myself that including a header file is just a labor-saving way of writing the information into the source file.

In contrast, Java 'import' is a totally different thing: it is more like 'using namespace' in C++.


4: When Any Header File Takes Effect


Hypothesizer 7
This is a matter of course, but let me remind myself anyway.

Wring a header file, itself, does nothing.

For example, when I have written a header file that contains a class declaration, I have not created the class (when I have written a Java source file that contains a class, I deem that I have created the class).

I mean, any header file has an effect only when it is included in a source file.

The difference may seem to be subtle, but it is important for considering the subsequent sections.


5: The Implications of What Are Put into Header Files


Hypothesizer 7
What can and should be put into header files? What can and should be put into source files?

Well, while any header file is really just a way of putting some things into source files via being included, the point of the header file is that it is meant to be included into multiple source files. Certainly, one could create a header file that is meant to be included into only a single specific source file, but such a tactic does not accord with the purpose of header file (why are the contents of the header file directly written into the source file?).

So, the implications of what are put into header files are that they are OK if they are in multiple source files.

For example, the definition of any global variable should be in a source file instead of in a header file, because it is a single entity and the single entity can be defined only once. In fact, this causes a linking error.

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/GlobalVariables.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_GlobalVariables_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_GlobalVariables_hpp__
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace usingHeaderFilesTest1 {
				int g_integer {1};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/SourceFileA.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/GlobalVariables.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace usingHeaderFilesTest1 {
		}
	}
}

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/SourceFileB.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/GlobalVariables.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace usingHeaderFilesTest1 {
		}
	}
}

The situation is the same with the definition of any function or anything that is not of the file scope: the duplicate of it is not allowed and not wanted.

"file scope"? Well, for example, the definition of an in-file 'static' variable is allowed in a header file, like this.

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/InFileStaticVariables.cpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_InFileStaticVariables_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_usingHeaderFilesTest1_InFileStaticVariables_hpp__
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace usingHeaderFilesTest1 {
				static int f_integer {1};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/SourceFileA.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/GlobalVariables.hpp"
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/InFileStaticVariables.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace usingHeaderFilesTest1 {
		}
	}
}

theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/SourceFileB.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/usingHeaderFilesTest1/InFileStaticVariables.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace usingHeaderFilesTest1 {
		}
	}
}

Typically, what are put into header files are declarations, which can be written multiple times in some source files, instead of definitions.

What is the difference between definition and declaration? . . . I will consider it in the next section.


6: The Difference Between Definition and Declaration


Hypothesizer 7
Any definition is something that begets an objective object.

"objective object"? . . . Well, I said so in order to distinguish it from what exist only in source files.

For example, someone may retort, "Also any declaration is an object, because it exists right here in the source file!".

Yes, it exists source-file-wise, but it does not beget any object at run-time.

Sometimes, definition is explained as something that allocates a memory area. . . . That explanation is not probably inaccurate, but I am not sure.

On the other hand, declaration does not beget any objective object, and is an assertion that something exists somewhere else or a blueprint.

For example, 'extern int g_integer' asserts that a global variable of that type and that name exists somewhere else, without begetting the variable by itself.

What is "blueprint"? . . . Well, any blueprint itself is not any objective object, but a template with that, objective objects can be begotten (I used "blueprint" instead of "template" in order to avoid confusion with that C++ specific 'template').

In fact, I am thinking of class declaration: any class declaration is not any assertion that the class exists somewhere else (it does not exist anywhere else!).

But does any class declaration not create a class (beget a class as an objective object)?

No, it does not, which is the point. In fact, if it did, it would be a problem, creating multiple duplicates of the same class in multiple object files.

Java class is an objective object, which is the reason why we can do like 'ClassA.class', while we cannot do that in C++, right?


7: The Reality of Class


Hypothesizer 7
Class in C++ is not any object, but a blueprint, which means that declaring a class itself does not create any object, and only when I create a class instance (doing like 'new ClassA ()') using the blueprint, the class instance is begotten according to the blueprint.

So, the class does not exist objectively, although some class instances may exist.

That is the reason why we can put class declarations into header files and include them into multiple source files.

We have to define class non-inline methods in source files, because they are (proper) functions and any (proper) function is an objective object.

Any inline method is not something to be defined but to be declared, because it does not exist as any objective object, but is exactly a blueprint for tweaking method calls, that is the reason why any method implemented in any header file automatically becomes inline.

The reason why any static field has to be defined in a source file is obvious now: the class declaration does not create anything and also any class instantiation does not create the static field (because the class instantiation is about the instance), and the static field has to be created somewhere. On the other hand, any instance field is created at the class instantiation.


References


<The previous article in this series | The table of contents of this series | The next article in this series>