• 12
• 12
• 9
• 10
• 13

Help me understand .cpp vs .h in C++

This topic is 1820 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

#ifndef LEVEL_H_INCLUDED
#define LEVEL_H_INCLUDED

#include "sdl_functions.h"

class Level {
public:
Level();

int width, height;
int map_grid[20][20];

void display_grid();
};

#endif // LEVEL_H_INCLUDED


Separate your .h and .cpp files again and use the version I have posted. Does this help any? Or change any of the errors?

Share on other sites

TTFN

Edited by Poigahn

Share on other sites

class Level {
public:
Level();

public :

class Level{....

This makes this Class Public to the calling program

class Level{

public:

Level()

only makes this class public inside the current header file, as in your second example.

This is simply wrong. public, private and protected only take effect inside classes and/or structs, a class is always "public" where you include it. So he got that one right.

@Topic:

    #ifndef LEVEL_H_INCLUDED
#define LEVEL_H_INCLUDED

#include "sdl_functions.h"

...

#endif // LEVEL_H_INCLUDED


You can simply put "#pragma once" at the top of your header file. Make sure everything is included beyond that, then your errors should be solved.

Edited by The King2

Share on other sites

TTFN

Edited by Poigahn

Share on other sites

This is a hard topic unless you know just a bit about what's going on behind the scenes when compiling. What happens is basically the compiler looks at what .cpp files you have in the project (or folder or the ones you tell it to compile, this depends), turns each one of them into an object file (.obj with visual studio, .a and .o also exist with some compilers IIRC) and it's done. After this the linker jumps onto the scene and starts looking at what pieces of code it needs to make the program into an actual executable - this is why you, for example, tell the linker, not the compiler, where the execution starts. Here you can have so called statically linked libraries too, that's the list of .lib files you feed to your linker. The most usual linker errors occur when you either forget to link against something or have multiple definitions - the same variable or function exists in several object files and the linker can't figure out which one to use.

Now, you might notice I didn't mention headers at all. This is because a header is never compiled itself, it's just included into (usually several) other files. So if your header has a global variable definition (for example "int x = 5;"), it'll be in all of the .cpp files and the linker won't like this. So you want your headers to only hold declarations, not definitions. int foo(int x); is okay, int foo(int x){return x*2+1;} isn't. Unless it's code you only use in one file, though then you don't even have to keep it in the header at all.

Edited by powly k

Share on other sites

This is kind of a tricky thing to explain, but when you understand it, you will understand how executables and libraries work much better. The solution to your problem has already been posted, but if you want the long explanation of what's going on, here it is! The main thing you need to understand is how the compiler and the linker work together. Pay close attention to the words declare and define.

The compiler turns all your source code into object files (as mentioned earlier), which contain actual executable code (basically). For each method, a callable segment of machine code is created. However, within most functions, there are also calls to other functions. Since there is no need to reproduce the called function (inline functions excepted), a dependency is created on the code of the called function. So for instance, the first two lines of following function will be compiled, but one external dependency exists.

foo.cpp

void foo() {
int x = 0;
x = x + 2;
bar(x);
}

You will have a compilation error if this was the entirety of the source file, since bar(int) is never declared or defined. So, either you must define it in this source file, or declare it with a prototype with the signature bar(int). Here's where the linker comes in:

Suppose bar(int) is defined in another source file. You need a way of telling the compiler not to worry about it right now, but that it can count on it existing when the final executable is being built (it's the linker's job to connect unresolved functions/variables to their instantiations), so that's what a header file does:

bar.h

#pragma once
void bar(int x);

We have now declared bar(int). We now need to look at foo.h:

#pragma once
#include "bar.h"
void foo();

And of course we need to include foo.h in foo.c (I didn't show that earlier since I wanted to make sure that it would be understood that the compiler would have no way of knowing that bar(int) existed at that point). So, what happens is that the compiler is told: 'there will be a method with the signature bar(int)', so you can make that call in the code. Now, as far as we've gotten, we still haven't defined bar(int), so if we tried to build as is, the compilation process would go fine, but we would get an unresolved external symbol: ...bar(int) from the linker. The reason is that the linker's job is to find all the unresolved calls and resolve them. This means: find the compiled code for bar(int) and actually jump to it ('link' it). To fix the problem, we must define the function with the matching signature:

bar.cpp

#include "bar.h"
void bar(int x) { x++; } // now we've generated some code for the linker to 'link' the previously unresolved dependency to

Now the code compiles and links. So, we've covered half of your problem, the other half is that the linker was unable to resolve the external variables that you were trying to access. So, if you understand everything so far, it's just more of the same. An extern variable means that the compiler will allow usage of a variable without actually knowing anything about it (starting with a new set of code):

foo.h:

#pragma once
extern int foo;

foo.cpp:

#include "foo.h"
int foo;

bar.h:

#pragma once
#include "foo.h"

bar.cpp:

#include "bar.h"
void bar() {
foo = 5; // this can be resolved by the linker since we know about foo from foo.h through bar.h
}

We can see that foo is a global int that anything that includes foo.h is able to access. The linker resolves the variable when creating the final executable. Alpha_ProgDes gave the right way to fix your problem (since your source file would now know about the extern SDL_Surfaces, which would allow the function/variables to be used).

The reason your old code worked was lucky inclusion order. Depending on the order that things are compiled, you can cheat the system, but this is a dangerous way to go about things, as the build will become very fragile based on the order of inclusions (certain dependency graphs are also impossible using that method). Projects built like that may, in effect, have *one* source file (aka compilation unit, since #include literally inserts source into a file), so everything must be defined in the right order instead of the proper way where it doesn't matter, since you're using the compiler and linker correctly. One other advantage you'll find in a large project is that where you used to have to recompile the entirety of the source for any change you made (since it was in effect one compilation unit), now your project can compile *only* the source that has changed, which is really great (can you tell I learned all this the hard way?).

This also explains libraries. Libraries are just compiled code. The header files for a library are just there to let source code depend on the library, and the linker will find the code and resolve the dependencies. For dynamic libraries, the 'cannot resolve dependency' error will come at run-time (eg when you're missing a dll), but it's the same principle.

I've sadly left out quite a bit, and used C for brevity, but C++ works the same way. In short:

• functions/methods (code units with a particular signature), and variables with any particular name can only be *defined* once in a compilation unit. A compilation unit is the entirety of the code being compiled (ie after all the #included files are inserted to make one big 'unit')
• to get code to compile, there *must* be *either* a definition or a declaration within the compilation unit.
• any variable/function that is only declared (not defined) within a compilation unit *must* be resolved by the linker if it is used (ie you can declare something and never define it if you never try to use it anywhere).
• a declaration *must* be resolved for an executable to be generated. If the definition is not within the compilation unit, it is the linker's job to resolve the dependency.
Edited by giant city games