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

Started by
19 comments, last by JohnnyCode 11 years ago
Having trouble getting my head around the concept of separating code into .cpp and .h files. I've been coding in C++ for a while now and have never done anything other than just stick all code into header files, which I guess I'm going to have to grow out of eventually.

Here's an example of the problems I'm having. This is the level class for a toy game. From what I have been reading I believe I'm supposed to split it into level.h and level.cpp something like this:

level.h

#ifndef LEVEL_H_INCLUDED
#define LEVEL_H_INCLUDED

class Level {
    public:
    Level();

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

    void display_grid();
};

#endif // LEVEL_H_INCLUDED

level.cpp

#include "level.h"

Level::Level() {
    width = height = 20;
    for ( int i = 0; i != width; ++i ) for ( int j = 0; j != height; ++j ) {
        if ( i == 0 || i == width-1 || j == 0 || j == height-1 ) {
            map_grid[j] = 1;
        } else {
            map_grid[j] = 0;
        }
    }
}

void Level::display_grid() {
    for (int i = 0; i != width; ++i ) for ( int j = 0; j != height; ++j ) {
        if ( map_grid[j] == 1 ) draw_sprite( i*64, j*64, block_blue, screen );
    }
}

When I do this, the compiler doesn't like level.cpp, saying 'draw_sprite', 'block_blue' and 'screen' have not been declared. 'draw_sprite' is a function and 'block_blue' and 'screen' are SDL_Surfaces, and they have been declared in an earlier include ("sdl_functions.h"). Attempting to #include sdl_functions.h again at the start of level.cpp prompts the compiler to get angry about me trying to re-declare a load of stuff.

What I don't understand is that if I just mash level.h and level.cpp together into a single header file:

level.h

#ifndef LEVEL_H_INCLUDED
#define LEVEL_H_INCLUDED

class Level {
    public:
    Level();

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

    void display_grid();
};

Level::Level() {
    width = height = 20;
    for ( int i = 0; i != width; ++i ) for ( int j = 0; j != height; ++j ) {
        if ( i == 0 || i == width-1 || j == 0 || j == height-1 ) {
            map_grid[j] = 1;
        } else {
            map_grid[j] = 0;
        }
    }
}

void Level::display_grid() {
    for (int i = 0; i != width; ++i ) for ( int j = 0; j != height; ++j ) {
        if ( map_grid[j] == 1 ) draw_sprite( i*64, j*64, block_blue, screen );
    }
}

#endif // LEVEL_H_INCLUDED

...then it works?

Obviously there are huge gaps in my knowledge as to how header and source files are supposed to interact (as well as in many other areas :P), so please go easy on me for asking a very stupid question. Thanks.
Advertisement

Hey. So the purpose of seperating your code in headers and source (.h/.cpp) is code organization and also lets say you are developing a library to distribute to other developers. So you can make a lib file with a header and people would know what functions are available to them and your logic is hidden.


#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?

Beginner in Game Development?  Read here. And read here.

 

TTFN

Your Brain contains the Best Program Ever Written : Manage Your Data Wisely !!

class Level {
public:
Level();

Should read :

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:

Instead of


    #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.

class Level {
public:
Level();

Should read :

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.

That's not how C++ works, and that code won't even compile. It will break things. The original code is correct (in this regard). In C++, #include statements literally copy and paste the included file into the current file when compiling, so there's no real distinction between "what's public in a header file and what's not" seeing as the file's contents are literally copy and pasted in a #include statement.

It sounds like the problem is in "sdl_functions.h" and judging by the error, it doesn't sound like the real problem is multiple declarations, but instead multiple definitions (there's a subtle difference, and it's possible the compiler's error message isn't too clear).

Rule 1: Don't define global functions or variables in a header. Define them in a single .cpp file (or spread across multiple .cpp files, but only give one definition to each global function and variable).

Rule 2: If you have global functions or variables defined in one .cpp, and you want to use them in another .cpp, create a header and declare the global functions or variables extern. You can google declaration vs definition (and what forward declaration is) for more information.

Rule 3: In some cases, you don't want to follow rules #1 and #2, and in these special cases you should mark the global functions or variables as static (or put them in an unnamed/anonymous namespace) and define in a .h file that you include in other .cpp files.

There are a lot of details I'm leaving out and I'm trying to make these rules very simple (and I don't have time right now). I wrote a blog post about the static modifier once, and I'm guessing that the last case for the static modifier that I talked about applies here.

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

TTFN

Your Brain contains the Best Program Ever Written : Manage Your Data Wisely !!

Like I said I was not up on my CPP and also asked for help in clarification, So I guess by trying and asking fo help you get knocked down.

This is why people do not try.

Which is unfortunate. Taking a little risk and being wrong can be a great learning experience (if you take the criticism right, and of course, if your mistake doesn't kill people :) ).

I have been out of CPP for awhile and I am trying to get back into it. I realize newer versions of CPP have changed and so has the format.

C++ has been like this for decades... Granted, C++ wasn't standardized as a language until 1998, so if you learned C++ before 1998, it's possible you learned someone's custom flavor of C++.

When I learned, each function had to be listed at the beginning of the program like such.

Int DiceRoll()

Void Showresults()

Sounds more like C than C++.

Now I am not seeing that in the code. So I was incorrect, again stating that I was uncertain of that. Hoping for help myself not votes down.

THanks!!

I don't want to derail the thread (and I fear I may be doing that!), but I'll give a little insight here. I voted your post down because it's technically incorrect. Not because I hate your or think you're stupid for trying (I don't!). I just want to make it clear to anyone quickly skimming this thread that the technical accuracy of that post is... lacking. I don't think people will gang up on you and give you a million -1 votes, but I might suggest editing your post and adding a strikethrough all the part about "public" and adding a note that yes, you were mistaken (I've done this when I'm wrong on something, and I've never been "gang downvoted" after doing so (though I usually get one or two -1 votes so that it's clear to the people of the internet that something was wrong)).

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

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.

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.

This topic is closed to new replies.

Advertisement