tjwrona
tjwrona

Reputation: 9035

Not including the corresponding header (.h) file in the implementation (.cpp) file still compiles?

I wrote up a quick example today just to see if it would compile and I was actually quite surprised when I found that it did!

Here is the example:

hello.h

#ifndef HELLO_H
#define HELLO_H

// Function prototype
void say_hello();

#endif

hello.cpp

NOTE: This does NOT include "hello.h" like it would in every C++ example I have ever seen in the history of forever!

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

void say_hello() {
    std::cout << "Hello!" << std::endl;
}

main.cpp

#include "hello.h"

int main() {
    say_hello();
}

I then compiled "hello.cpp" into a static library as follows:

g++ -c hello.cpp
ar -rvs libhello.a hello.o

Then I compiled the "main" application and linked it to the library

g++ -o main main.cpp -L. -lhello

And ran it and it executed fine!

./main

Hello!


While I was surprised... I do understand why this works. It is because the function in "hello.cpp" is not declared static so it has external linkage and can be seen from outside. Making it static will cause the link to fail due to an undefined reference.

So here's the question... If this does work, then why does everyone everywhere ALWAYS include the ".h" header file with the function declarations in the ".cpp" implementation file. Clearly if it is just defining free functions, this is not necessary and everything will work fine if the header file is not included.

So why do we always include it? -- Is it simply a general lack of understanding of how the linker works? Or is there something more?

Upvotes: 1

Views: 250

Answers (2)

Let us change your hello.cpp:

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

This will compile just as well as the previous version. It will probably link too - but it isn't right. The return type is wrong.

This is undefined behaviour, but in many common implementations, you will get away with it because you don't use the return value, and it is often returned in a register. However, it doesn't have to be - and you may get very strange errors at run time. Particularly if the difference is a bit more complicated (like returning double when the callers expect int - that will often be returned in a different register).

If on the other hand, you had written:

#include "hello.h"

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

Then the declaration in the header file would not have matched the definition in the CPP file - and you would have got a nice, easy to understand, compiler error message.

In fact, this is such a good idea that GCC will complain if you don't have a declaration of an external function. (And if you have -wall -werror on your command line, it will stop your build.)

Upvotes: 2

metal
metal

Reputation: 6332

If you have a class, you'll want to include it to get the declaration of the class and its members for their definitions to match. Otherwise, you won't be able to separate the definition and declaration.

/// C.h
class C
{
public:
    C();
private:
    int _i;
};
/// C.cpp
// #include "C.h"

C::C() : _i(42) {} // error: 'C' does not name a type

See it fail on Coliru.

Likewise, if you have a class template or a function template, it usually needs to be in a header so that versions of it can be stamped out later.

Upvotes: 0

Related Questions