Archived

This topic is now archived and is closed to further replies.

alnite

header hell

Recommended Posts

does somebody else feel annoyed by #include here? especially when you start having #include <me.h>, where me.h #include <you.h>, where you.h #include <she.h>, where she.h #include <he.h>, and so on... Everything went so nice and organized at the beginning, but once I add another layer, things get a little bit messier. I add another layer, a little bit more messier. Until I got confused myself, what are the classes that this particular module #include? Got tips?
Current project: A puzzle game.
% completed: ~10%
Status: Active.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Here is how I manage my header files and code. I try to be as modular as possible while not making it insanely tricky.

Nearly all my header files have #include "Prerequisites.hpp" which links to StdHeaders.hpp and Config.hpp. This allows me to declare my classes there, include headers like windows.h for all my files.

Then I do my best to be modular and nearly every .cpp file have their own .hpp file which they link to. this .hpp file links to Prerequisites.hpp, and sometimes to one or two other.hpp files. It is extremely vital that the parts of your code be independent of each other so that you may reuse some code in another project and thus allowing you to get to the fun or meat and potatoes of the program faster.

If you are coding in C++, templates are great to allow you to code modularly. Use classes and make sure you know how public: protected: and private: work. C++ does allow for multiple inheritance but often what that does is tie your code even more, so I would say it is okay to use inheritance but don''t go crazy with class a inheriting from b that inherits from c that inherits from d. Inheritance is extremely useful but sometimes it can be used the wrong way.

Hope that helped,

Good luck.

PS: What you can do too, is have one header file that includes all your program header files. I have no idea what the downside on that is but you sure wouldn''t have any more problem in getting mixed up.

Share this post


Link to post
Share on other sites
Plan your game entirely . In my latest project Spatra (download at my site) I planned the structuring too basicly and that was not enough. So while I worked on it I added a few new classes as time went and at last I had lost the absolute structuring that I wanted. I would say that the code is well structured but I strive for the perfect. You may download the source of Spatra which is also uploaded on my site and study how i have structured it.


____________________ ____ ___ __ _
Enselic's Corner - My site. Go test my game Spatra and see if you can beat it onto the Official Spatra Top 10.
CodeSampler.com - Great site with source for specific tasks in DirectX and OpenGL.

[edited by - Enselic on May 4, 2003 2:08:10 PM]

[edited by - Enselic on May 4, 2003 2:08:33 PM]

Share this post


Link to post
Share on other sites
Here are my tips.

1. If you are going to use a type in a code file, then make sure you have the header for that type explicitly included at the top of that code file. Don’t depend on another header including it for you. What I mean is this. Say that in your code file you need to use both the “Me” and “You” classes. At the top of the code file you already have “Me.h” included. Although this will include “You.h” as well (because “Me.h” includes “You.h”), don’t rely on that. You should still include “You.h” explicitly. The reason is because someday you may change “Me.h” so that it no longer includes “You.h” and that will break a lot of code. It’s a pain to track down all the broken code. Of course, to prevent actually compiling headers twice, you should put a sentry at the top of all header files. In Microsoft C++ you can use “#pragma once” for this.

2. For the complementary rule to the one above, I’ll quote from Effective C++ by Scott Meyers since he says it so succinctly: “Don’t #include header files in your header files unless your headers won’t compile without them.” So for example, don’t #include “You.h” in “Me.h” unless “Me.h” needs it.

3. It is not always necessary to include a header file (with the entire class definition ) when you use a class. Sometimes you can get away with just using a declaration at the top of the code file. This reduces compilation dependencies. Here an example from Effective C++ :


class Date;

Date returnADate();
void TakeaData(Data d);


Note that it’s not necessary to #include “Date.h” for this to work. You usually only need the class definition if you’re going to call a method or make a derived class. You don’t need it just to pass around objects or pointers and references to objects.


I really recommend Scott Meyers’ books Effective C++ and More Effective C++ . No C++ programmer should be without them.


[edited by - JimH on May 4, 2003 5:07:53 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by JimH
2. For the complementary rule to the one above, I’ll quote from Effective C++ by Scott Meyers since he says it so succinctly: “Don’t #include header files in your header files unless your headers won’t compile without them.” So for example, don’t #include “You.h” in “Me.h” unless “Me.h” needs it.

That''s what I have been doing. But if I #include "me.h" in other headers, "she.h", and then "he.h" also #include "me.h", then I have "another.h" #include both "she.h" and "me.h", then "anotherone.h" #include "another.h" and "me.h"...things get really really confusing. The program compiles...but when I make changes to some header files, almost the whole project files get re-compiled.

Maybe Enselic was right, I need to plan my game and engine entirely, but I just hate playing around with UML and not coding, lol.


Current project: A puzzle game.
% completed: ~10%
Status: Active.

Share this post


Link to post
Share on other sites
planning a whole project is pretty impossible and doing it just to sort out your source files is a bit over the top.

The simple rule I follow is:

'a single header should be able to stand alone'

This means that if you need something that is in a particular header you just include that header. You don't need to think about including other headers first to make the one you want to use work.

eg. What do we need to do to get this header to work? You can try writing it and an accompanying test.cpp file which only includes test.h and see what you need to get it to compile.


          
//test.h

struct test : public base {
public:
test();
void say(std::string greeting);
void doSomething(Dog& dog);
void doSomethingElse(Cat cat);
};

//test.cpp

#include "test.h"
// won't compile



The first thing we can do is put include guards at the beginning and end of the header.
#ifndef PETEWOOD_TEST_H
#define PETEWOOD_TEST_H
...
#endif

These use the C preprocessor to place a name definition (PETEWOOD_TEST_H) in the file. If you inlcude this header multiple times in a cpp file, then it won't cause problems. After the first time it is included the check for PETEWOOD_TEST_H will cause the declaration of struct test to be skipped over.

The second thing we need to do is tell the compiler about the base class. It needs to know all about the class (namely its size) so cannot be satisifed with a simple forward declaration. You need to include "base.h"

The third thing is the use of std::string. I'll come back to this in a bit though.

The fourth thing is the reference to a Dog class. References and Pointers are very similar. To satisfy the compiler all it needs to know is the name of the class. Once you start making use of the pointer by calling functions on it the compiler does need to be able to see the full declaration. That's not the case here and a simple forward declaration is all that's needed. Passing by reference or pointer always passes a pointer sized amount of data (usually 4-bytes if you're using MSVC).

edit: ignore this fifth point (see Jim's post below). A forward declaration is all that's needed.
The fifth thing is the Cat class. It is passed by value and that means the compiler needs to be able to see the full declaration (again because it needs to know its size and the right amount of data gets passed to the function). So you need to include the header "cat.h". This is a design descision which may be leading to the problems and frustrations you are having. Having to include the "cat.h" header is a needless coupling between "test.h" and "cat.h". If "cat.h" changes then "test.h" is effectively changed and anything dependent on it will have to be recompiled. What can you do?

If you change the function so that it takes a reference (or pointer) to a Cat instead you can, as with Dog, provide a forward declaration rather than including the header. You might then say 'but the function can then modify my cat'. So you pass it by const reference (or pointer) to show that it won't be changed.

Okay, coming back to std::string. This is from the standard c++ library. The compiler needs to know the whole declaration as we are passing by value. It needs the header. Can we do the same as with Cat above? That is, instead of passing by value, passing by reference (or pointer). Passing by reference is advisible anyway as it avoids making a a copy of the string (passing builtin types like ints and doubles by value is okay but anything else is probably better by reference). So, if we pass it by reference we just have to forward declare std::string. However this isn't allowed (your compiler may allow it although I hope not). The std namespace is resereved only for library implementors to define and you're not allowed to add anything to it.

So you'll actually have to include the string header.

Here's the modified header


  
#ifndef PETEWOOD_TEST_H
#define PETEWOOD_TEST_H
//test.h


#include <string>

#include "base.h"

class Dog;
class Cat;

struct test : public base {
public:
test();
void say(std::string& greeting);
void doSomething(Dog& dog);
void doSomethingElse(const Cat& cat);
};

#endif

//test.cpp

#include "test.h"
// compiles fine



This is a header with almost the minimum amount of file dependencies. If you follow this method you'll have almost the smallest amount of recompiles. If something in a header somewhere changes, then the only things that have to be recompiled are the things for which it really matters to them. So if "base.h" changes you'll get a recompile of anything using "test.h". But if "dog.h" or "cat.h" files using only "test.h" won't be recompiled.

If you are using a system that supports pre-compiled headers there are a few other things to consider. You could but all the standard headers and other commonly included headers from your project, that are stable and don't change, in one file. So for example put <string> in there and not as in the above example. Then you include the precompiled header in every cpp in your project. You have to put it before all the other includes so that, for example, the string header could be seen in the following test.h file. This of course means your files are not as self sufficient as the other method encourages.

Hope that's not too much for you. It's simple and effective and everyone has to do it so don't get too annoyed.

[edited by - petewood on May 5, 2003 6:57:37 AM]

Share this post


Link to post
Share on other sites
quote:
Original post by alnite
That's what I have been doing. But if I #include "me.h" in other headers, "she.h", and then "he.h" also #include "me.h", then I have "another.h" #include both "she.h" and "me.h", then "anotherone.h" #include "another.h" and "me.h"...things get really really confusing. The program compiles...but when I make changes to some header files, almost the whole project files get re-compiled.

You can only do as much as you can do. Sometimes you do have a chain of includes that you can’t help. But you can reduce them by following the tips that people pointed out. Unless you have a small program, the entire thing should not need to be recompiled when you change a header (unless you change something like object.h or mydefs.h that is legitimately used by every file).

Also, the way you design your program can also have an effect on the interdependencies of classes and consequently of files. More or less, you try to reduce the coupling between classes and modules. The more modular your program is, the fewer interdependencies there will be and your program won’t end up totally recompiling every time you touch a header file. I guess that’s what Enselic was referring to regarding planning. Although, I find it’s only practical to design down to a given detail level. But that’s another subject.
quote:
Original post by petewood
The fifth thing is the Cat class. It is passed by value and that means the compiler needs to be able to see the full declaration (again because it needs to know its size and the right amount of data gets passed to the function).

That’s a nice example you wrote out. At the risk of sounding too picky, I just thought I’d point out one thing. Even when Cat is passed by value, you don’t need to #include Cat.h within the test.h header file. That is, you don’t need Cat’s class definition to declare a function that uses Cat by value. But you will need to #include Cat.h at the top of test.cpp for the reason you mentioned: in test.cpp the compiler needs to make a copy of a Cat object so it needs to know about it.

[edited by - JimH on May 4, 2003 10:53:48 PM]

Share this post


Link to post
Share on other sites
Hm...I didn''t know that forward declarations can be so useful. Yes, I usually #include "cat.h" in every class that needs Cat class. So, when I make changes in Cat class, the other classes get recompiled.

I''ll try using forward declarations...


Current project: A puzzle game.
% completed: ~10%
Status: Active.

Share this post


Link to post
Share on other sites
quote:
Original post by JimH
That’s a nice example you wrote out. At the risk of sounding too picky, I just thought I’d point out one thing. Even when Cat is passed by value, you don’t need to #include Cat.h within the test.h header file.


Thanks Jim. You''re absolutely correct. Maybe I shouldn''t post at two in the morning (c:

So scrub section five. A forward declaration will suffice for the Cat.

Pete

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
This is how we do it.
We put header files that are used frequently in a "common.h"
header file that is included by all other.

Functions that are only used in one or two places we usually declare extern where we use them.

Cheers

Share this post


Link to post
Share on other sites
quote:

Functions that are only used in one or two places we usually declare extern where we use them.



What happens when you change the function prototype? (Yes, I know it''s uncommon, but still...)

Share this post


Link to post
Share on other sites
quote:
Original post by tr0n
[quote]
Functions that are only used in one or two places we usually declare extern where we use them.



What happens when you change the function prototype? (Yes, I know it''s uncommon, but still...)

Shit happens
Error happens
Bad things happen
A$$ gets kicked

Share this post


Link to post
Share on other sites
Like people said, put as many headers into your cpp files as you can and use forward declarations w/pointers or references to objects. Under vc++ you can put system/your libs headers into precompiled header to save compilation time. One problem I ran into was using class enums for which I haven''t found a solution other than changing enums to ints and use some descriptive name for the int vars.

Share this post


Link to post
Share on other sites
quote:
Original post by JD
Like people said, put as many headers into your cpp files as you can

Uh, no, the idea is to put as few in as you can. Maybe you didn''t quite mean what you said. Use as many headers as you need, no more, no less. That rule applies both to .CPP and .H files.

quote:
Under vc++ you can put system/your libs headers into precompiled header to save compilation time.

Only do this for headers that you won''t be changing regularly or at all.

quote:
One problem I ran into was using class enums for which I haven''t found a solution other than changing enums to ints and use some descriptive name for the int vars.

You''ve said how you solved it, but you didn''t say what the problem actually was...

[ MSVC Fixes | STL Docs | SDL | Game AI | Sockets | C++ Faq Lite | Boost
Asking Questions | Organising code files | My stuff | Tiny XML | STLPort]

Share this post


Link to post
Share on other sites