Timilearning

Learning C++ from Java - Header files

· 6 min read

This is a continuation of the series on C++ topics that I've found interesting, coming from a Java background. You can read the first post here.

I'll start this post by describing forward declarations in C++ before talking about header files.

Table of Contents

Say we write the program below to print the elements in a list:

#include <iostream>
#include <vector>

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);
}

void print(std::vector<int> list)
{
std::cout << "Your integer list contains the numbers: "
<< "\n";

for (int listItem : list)
{
std::cout << listItem << "\n";
}
}

This simple program will not compile. My compiler produces the error:

‘print’ was not declared in this scope; did you mean ‘printf’?

This is because the compiler, which parses code from the top down, needs to know about an identifier before it encounters the identifier's usage.

In the above example, the compiler encounters print()'s usage in main() before its declaration (and definition), and does not know what print() is yet.

There are two ways to fix this:

  1. Reorder the function definitions so that print() comes before main().
  1. Use a forward declaration, which I'll focus on.

Forward declarations

Quoting Learn C++:

A forward declaration allows us to tell the compiler about the existence of an identifier before actually defining the identifier.

We forward declare a function by specifying its prototype, which comprises the function's name, return type, and parameters. We can also forward declare variables and user-defined types.

Rewriting the previous example to use a forward declaration:

#include <iostream>
#include <vector>

void print(std::vector<int> list); // forward declaration of print()

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);
}

void print(std::vector<int> list)
{
std::cout << "Your integer list contains the numbers: "
<< "\n";

for (int listItem : list)
{
std::cout << listItem << "\n";
}
}

The compiler now knows about print() before its usage in the main() function, and all is perfect.

Working with multiple files

Forward declaration also applies when working with multiple files. We can split the previous example into two files:

print.cpp:

#include <iostream>
#include <vector>

void print(std::vector<int> list)
{
std::cout << "Your integer list contains the numbers: "
<< "\n";

for (int listItem : list)
{
std::cout << listItem << "\n";
}
}

And main.cpp:

#include <iostream>
#include <vector>

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);
}

Similarly, this program will not compile unless you forward declare print() in main.cpp as before:

#include <iostream>
#include <vector>

void print(std::vector<int> list);

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);
}

Multiple declarations, single definition

You can declare an identifier in different files across your program, but it can only have one definition. This is the 'One Definition Rule' in C++.

If you declare an identifier in a file but don't define it anywhere in your program, the compiler will compile the file, but the linker will fail. For example, after removing the print() definition in print.cpp above and attempting to build the program, I got the error:

undefined reference to `print(std::vector<int...>)'

Header files

Extending the previous example, let's say we don't just want to print() an integer list, but we also want to assign it a score based on how many even numbers it contains.

We can rename print.cpp to container.cpp with the following content:

#include <iostream>
#include <vector>

double evenScore(std::vector<int> list)
{
int evenCount{};
for (int listItem : list)
{
if (listItem % 2 == 0)
{
evenCount++;
}
}

return (double) evenCount / list.size() * 100;
}

void print(std::vector<int> list)
{
std::cout << "Your integer list contains the numbers: "
<< "\n";

for (int listItem : list)
{
std::cout << listItem << "\n";
}
}

And then forward declare both functions in main.cpp:

#include <iostream>
#include <vector>

void print(std::vector<int> list);
double evenScore(std::vector<int> list);

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);

std::cout << "The evenScore of the container with sizes is: " << evenScore(sizes) << "%\n";
}

This works fine, but imagine how boring it would be to forward declare all the functions we need if container.cpp had ten functions.

Thankfully, C++ has header files which simplify this process. Header files have a .h or .hpp extension and you can declare all identifiers in a header file.

We can extend our running example by splitting container.cpp into a header and source file:

container.h:

#include <iostream>
#include <vector>

double evenScore(std::vector<int> list);

void print(std::vector<int> list);

We use container.h by #include-ing it in the source files:

container.cpp:

#include <iostream>
#include "container.h"

double evenScore(std::vector<int> list)
{
int evenCount{};
for (int listItem : list)
{
if (listItem % 2 == 0)
{
evenCount++;
}
}

return (double)evenCount / list.size() * 100;
}

void print(std::vector<int> list)
{
std::cout << "Your list has the following content: "
<< "\n";

for (int listItem : list)
{
std::cout << listItem << "\n";
}
}

main.cpp:

#include <iostream>
#include "container.h"

int main()
{
std::vector<int> sizes{12, 23, 32, 39, 43, 40};
print(sizes);

std::cout << "The evenScore of the container with sizes is: " << evenScore(sizes) << "%\n";
}

#include is a preprocessor directive that tells the preprocessor to paste the content of another file into the current file. In our example, main.cpp and container.cpp will contain the declarations in container.h after the preprocessor runs.

Note that I didn't need to include container.h in container.cpp, but I have seen it recommended that you include a header file in its matching source file and you can find an excellent demonstration of why here.

Header guards

Say just for fun, we are writing a program to compare Tidal and Spotify with the following rubric:

Assign a score to each platform based on the average length of the top K most played songs on the site, where K is user-provided and must be at least 2.

We can introduce a new function in container.h to calculate the average value and define a global variable for the minimum value of K.

Container

container.h:

#include <iostream>
#include <vector>

const int minimumSize = 2;

double average(std::vector<double> list);

void print(std::vector<double> list);

container.cpp

#include <iostream>
#include "container.h"

double average(std::vector<double> list)
{
double total = 0;

for (double listItem : list)
{
total += listItem;
}

return total / list.size();
}

void print(std::vector<double> list)
{
std::cout << "Your list has the following content: "
<< "\n";

for (double listItem : list)
{
std::cout << listItem << "\n";
}
}

Adding new platform-specific header and source files:

Spotify

spotify.h:

#include "container.h"

namespace Spotify
{
double calculateTopKScore(int k);
}

spotify.cpp:

#include "spotify.h"

namespace Spotify
{
double calculateTopKScore(int k)
{
if (k < minimumSize)
{
return -1.0;
}

//Assuming that k = 4 and after getting the data from Spotify, we have this list:
std::vector<double> songLengthsInSeconds{2.49, 5.53, 4.46, 3.35};

return average(songLengthsInSeconds);
}
}
Tidal

tidal.h:

#include "container.h"

namespace Tidal
{
double calculateTopKScore(int k);
}

tidal.cpp:

#include "tidal.h"

namespace Tidal
{
double calculateTopKScore(int k)
{
if (k < minimumSize)
{
return -1.0;
}

//Assuming that k = 4 and after getting the data from Tidal, we have this list:
std::vector<double> songLengthsInSeconds{3.06, 4.17, 6.44, 3.07};

return average(songLengthsInSeconds);
}
}

With these in place, we can write our main.cpp as:

#include <iostream>
#include "spotify.h"
#include "tidal.h"

int main()
{
int k = 4;
double spotifyScore = Spotify::calculateTopKScore(k);
double tidalScore = Tidal::calculateTopKScore(k);

std::cout << "The top " << k << " Spotify songs have an average length of " << spotifyScore
<< " minutes, while Tidal songs have an average length of " << tidalScore << " minutes. \n";
}

Can you tell why this program will not compile?

.
.
.

I get the error below which says minimumSize is redefined.

In file included from tidal.h:1,
from main.cpp:3:
container.h:4:11: error: redefinition of ‘const int minimumSize’
4 | const int minimumSize = 2;
| ^~~~~~~~~~~
In file included from spotify.h:1,
from main.cpp:2:
container.h:4:11: note: ‘const int minimumSize’ previously defined here
4 | const int minimumSize = 2;
| ^~~~~~~~~~~

This is because when the preprocessor runs, it #includes the content of the spotify.h and tidal.h into main.cpp, and both header files #include container.h.

Since container.h contains a definition of minimumSize, main.cpp will have two definitions of minimumSize after the preprocessor completes, which violates the One Definition Rule.

But if you rewrite container.h so that it contains the below, the program will compile fine.

container.h:

#ifndef CONTAINER_H // Note that CONTAINER__H can be replaced with any unique name.

#define CONTAINER_H

#include <iostream>
#include <vector>

const int minimumSize = 2;

double average(std::vector<double> list);

void print(std::vector<double> list);

#endif

The difference is container.h now contains a header guard, which tells the preprocessor to first check if CONTAINER_H is not defined in the current translation unit (#ifndef), and only then should it define it and include the content of the header file.

In main.cpp, when the preprocessor is processing container.h included in tidal.h, it will see that it has already defined CONTAINER_H in the translation unit from when it processed spotify.h, and will not (re)include container.h's content.

This simple example involves a variable, but header files may contain function definitions too and header guards help prevent multiple definitions of the functions.

In summary, header guards prevent a translation unit from having multiple definitions of an identifier.

Header files and linkage

I wrote about linkage in the previous post and will give another example here.

We can introduce a linking error in our music streaming application by adding the extern keyword before the variable in container.h:

#ifndef CONTAINER_H 

#define CONTAINER_H

#include <iostream>
#include <vector>

extern const int minimumSize = 2;

double average(std::vector<double> list);

void print(std::vector<double> list);

#endif

I get an error saying multiple definition of 'minimumSize'. This is because the minimumSize variable initially has internal linkage since it's a const global variable, meaning each translation unit will get its copy of the variable.

But by making it extern and giving it external linkage, we're telling the linker that each usage of the variable refers to the same instance, and since the variable definition will appear in spotify.cpp and tidal.cpp, we are violating the One Definition Rule.

We can maintain the external linkage property and fix the error by replacing the definition in the header file with a declaration, and defining the variable in only one of the source files we include the header in, as shown below:

container.h:

#ifndef CONTAINER_H

#define CONTAINER_H

#include <iostream>
#include <vector>

extern int minimumSize;

double average(std::vector<double> list);

void print(std::vector<double> list);

#endif

tidal.cpp:

#include "tidal.h"

int minimumSize = 2;

namespace Tidal
{
double calculateTopKScore(int k)
{
if (k < minimumSize)
{
return -1.0;
}

std::vector<double> songLengthsInSeconds{3.06, 4.17, 6.44, 3.07};

return average(songLengthsInSeconds);
}
}

There is now only one definition of minimumSize in the program and because it is extern, any usage of the variable will refer to the value we defined.

Further Reading

c++17

A small favour

Did you find anything I wrote confusing, outdated, or incorrect? Please let me know by writing a few words below.

Follow along

To get notified when I write something new, you can subscribe to the RSS feed or enter your email below.

← Home