This is c++; there's nothing 'automatic' about header files. When you do
#include "somefile.h"
when the compiler preprocessing runs through, it sees that, loads up somefile.h, then essentially copies the text and pastes it all right there.
Related, you use 'include guards' for most complex programs with either
#pragma once
// file contents
or some variant of
#if !defined( SOMEFILE_H )
#define SOMEFILE_H
// file contents
#endif
Since that copy-paste doesn't know if it's already happened before in that file. For example, if you had somefile.h and someotherfile.h, where someotherfile.h has an #include "somefile.h" and then you had a cpp file doing
#include "somefile.h"
#include "someotherfile.h"
You end up with 2 sets copied in without the include guards, which is then compiled, and probably complains about multiple copies of the same definitions. It's a very simple model of copy-pasting file contents, and so is quite easy to reason about.
When the compiler step runs, after doing the #include copy-pastes and some other things in preparation for the compile, it specifically is compiling the .cpp files into intermediate files one by one, with each file 100% independent and without reference to any other .cpp files anywhere. (this is also why compiling is extremely multi-core friendly) During this step, it builds up a list of references from everything that's been declared previously (so it knows it isn't just a syntax error/type) as essentially a "TODO: figure out where these things are" This stuff goes into the .obj files you probably noticed your compiler producing as an intermediate byproduct.
Then the linker runs and combines all the .obj and library files into the final program; it takes those symbol references and resolves them by looking around for a definition that provides all the necessary information.
As for what's necessary for these steps:
For the compiler, it does basic syntax matching as well as static memory allocation related stuff.
MyClass A; // This line fails to compile because MyClass is assumed to be a typo, since you never told it MyClass is some sort of symbol.
Foo(); // This line fails to compile for the same reason.
class MyClass; // declare the class without defining it; "there is a symbol called MyClass"
void Foo( int ); // declare the function without defining it; "there is a function called Foo which returns void and takes int as a parameter"
MyClass B; // This line fails to compile because MyClass is 'undefined' which means the compiler now knows it is a type symbol, but it doesn't know how big it is, and thus it can't figure out how much memory needs to be allocated here.
MyClass* C; // This line compiles just fine because it knows MyClass is a type, and it knows C is a pointer, which has a known size of 32/64 bits depending on platform. Memory allocation of a thing pointed to (if it ever occurs) happens elsewhere, so the compiler doesn't complain.
MyClass& D; // This line fails to compile because references need to be initialized to refer to something; but as shown in B, a mere declaration can't be used to create something to refer to, so these are out too.
Foo( 1 ); // This line compiles correctly because it knows Foo is a function symbol. Because calling a function requires no allocated memory, the compiler is entirely fine with this.
class MyClass // define MyClass symbol to mean 'a class consisting of an int member'
{
int a;
};
MyClass E; // This line compiles; after processing the previous line, the compiler now knows MyClass is a specific size, and so can allocate space for the variable E
MyClass& F = E; // Likewise, this compiles (of course) since we now have a value to reference.
Again, remember back to how header files work: just a copy-paste. So any of the preceding code could be moved into a header file and #include "thatheaderfile.h" used to copy them back in. (Side note: You can in fact copy any arbitrary code ((even inside of functions! even in the middle of a line!)) into a header file such that you just have some random code bits sitting out in the open in a .h file. If you ever do this in the workplace, you will be rightfully drawn and quartered, then hung in the four corners of the office as a warning to the rest. So yes, it really does behave like a copy-paste of a file's contents) Also of note, when the compiler runs through doing these things, it's scanning from top to bottom. This is why when you include a header file from a library, it needs to be above where it is used, and so by convention they are all dropped at the top of the file.
Then there's the linker that runs after the compile phase has made them into the compiled .obj files which links them all together. Taking the above example, all of the parts that compiled correctly would link correctly *except* for the call to Foo(). If, however, we had other .cpp files in the project and one of those .cpp files held the definition of Foo like:
void Foo( int a ) { return; }
the linker would successfully find the definition of the Foo symbol, without the need for the copy-paste which is .h file includes; otherwise the result would be a linker error reporting an 'undefined symbol Foo'
So in summary, things that go into header files:
1. Declarations of things you want the compiler to recognize as a symbol to link, rather than thinking it's a typo/syntax error. (externally visible functions, class member functions)
2. Definitions of things you want to be able to create instances of non-dynamically. (classes usually)
3. Templates -- not mentioned above, but it needs to figure this out at compile time, since it needs to figure out how much code to generate, which falls under category 2's broad 'memory stuff'.
Hopefully that gives you a broad overview for reasoning about declaration/definition of stuff as it relates to header files and C++'s code building system.