gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > What's the best way to write any given .h file?

#157288 - Polaris - Wed May 21, 2008 7:44 pm

This was pretty clear to me, up until the moment I read a bit of TONC. Particularly the part where it says, and I quote:
Quote:
Thou shalt not #include data or code!
The exclamation wasn't there, but I think it deserves it :P

This was a pretty shaking statement, and one that made me ask myself if I have been doing things properly all along. Like a lot of people, I enjoy doing things properly.

This is how I would write a regular .h that might be describing a player class or an enemy class or something along the lines.

Code:

/*include useful stuff here*/

/*define more stuff here*/

/*namespaces go here*/

class Thing {

    int positionX;
    int positionY;
    int velocity;
    int direction;
    int angle;
    int turnSpeed;

    void accelerate();
    void turnClockwise();
    void turnCounterClockwise();

public:
    Thing();
    ~Thing();
 
    void Update();
    void Draw();
};

Could that kind of stuff be potential trouble for medium sized projects and onwards?

And one more thing. I have seen this lately
Code:
#ifndef ANY_CLASS
#define ANY_CLASS
/*all the necessary stuff goes here*/
#endif


What's that for? Is it doing something similar to #pragma once?

#157291 - DekuTree64 - Wed May 21, 2008 8:27 pm

Polaris wrote:
This is how I would write a regular .h that might be describing a player class or an enemy class or something along the lines.
...
Could that kind of stuff be potential trouble for medium sized projects and onwards?

That's all perfectly fine. I think you're thinking of a different kind of "data" (member variable declarations).

The bit about #including data is referring to putting large const arrays in header files for graphical data and such, rather than using a file system or something.

But since you indirectly asked, there is some value in hiding member variable declarations for large projects, just so you don't have to #include so many things from header files and rebuild everything when one of them changes. For that, a common technique is the "pimpl" (private implementation) design pattern. Basically make a nested class defined in the .cpp file, and allocate it seperately:
Code:
Thing.h:

class Thing {
    class Impl;
    Impl *impl;

public:
    Thing();
    ~Thing();
 
    void Update();
    void Draw();
};


Thing.cpp:

class Thing::Impl {
public:
    int positionX;
    int positionY;
    int velocity;
    int direction;
    int angle;
    int turnSpeed;

    void accelerate();
    void turnClockwise();
    void turnCounterClockwise();
};

Thing::Thing() :
    impl(new Impl)
{
}

Thing::~Thing()
{
    delete impl;
}


And lastly, yes, the #ifndef thing is just another way of preventing multiple inclusion, like #pragma once.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#157292 - sajiimori - Wed May 21, 2008 8:30 pm

Do not include "useful stuff" from within headers. Do everything you can to minimize the number of headers you include from within headers -- try to reduce it to zero, when possible. Nested header includes will increase your header dependencies geometrically, so that modifying one header will force much of your project to recompile.

Make good use of forward declarations to eliminate includes. Choose key points to apply the pimpl idiom: it has technical costs, but when applied carefully, it can greatly reduce rebuilds without much performance loss.

For a zero-cost variant of pimpl, leave your data members in the header, and only move your private methods to the .cpp file. Declare a nested class, befriend it, and change your private methods into static methods of the nested class.

Beyond that, just minimize the quantity of stuff in the header. Move things to the .cpp file whenever possible.

Edit: Ah, Deku beat me to some of that.

#157293 - silent_code - Wed May 21, 2008 8:46 pm

being a #pragma once user for many years, some beatyful day i have come to the point, that i had to use the preprocessor guards.
i can't remember when, where or why it was, just that it made me change that habbit i had for a long time (i'm not *exactly* a friend of changing things) and since then, i haven't used #pragma once again.

although "#po" is widely supported and used, it's not standard and not as flexible (yes, it's shorter) as the guards.

i guess my situation was something along the following: when you have a fairly complex system, that, due to (sometimes poor) design choises, needs a lot of - let's call it: "cross inludes" (think: clark kent loves loris lane, loris lane loves superman, superman is clark kent, clark kent loves...) #po may become tricky in some cases. i experienced it and i have to say, even though i can't remember why #po didn't work, it ... didn't work. :^p

but guards have their own set of problems: they need names, what might cause problems etc.

in the end, you have to try it yourself. weather you prefer one approach over the other is a matter of taste *and* how you use it in the end.
or just use both.

happy coding! :^D

ps: i have just found a wikipedia (is it called:) article (?), that i want to reccommend to you. :^)
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#157295 - Polaris - Wed May 21, 2008 9:05 pm

Thanks for the replys guys. I'll have to look into that design pattern once i'm done fiddling with my small demos.

When you are talking about const arrays for graphical data, is it the same as extern const array? Because I never really understood why grit generated those arrays with extern before them.

I guess I'll also need to look into making a file system, even if I have no idea how it works, any good tutorials on that?

silent_code: Using both seems like the best thing. After all, if my program is running like a turtle, I don't think it will ever be because of some include guards.

#157297 - silent_code - Wed May 21, 2008 9:19 pm

you seem like a quite new and fresh programmer, from what i read (excuse me, if i'm wrong!)

i wouldn't reccomend in programming your own filesystem. simply use one of the available like libfat or it's derivates, instead. ;^)

ps: if you declare something extern, that means the same "thing" gets linked everywhere its declaration is included, in contrast to a new "thing" (symbol) being created for each "unit" (think .cpp file), which most likely *will* cause trouble.
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#157299 - Cearn - Wed May 21, 2008 9:43 pm

Polaris wrote:

When you are talking about const arrays for graphical data, is it the same as extern const array? Because I never really understood why grit generated those arrays with extern before them.

`extern' anything isn't really data, it's just a sign to the compiler that something with that name is supposed to exist somewhere in the project, and says how to interact with it. For example, compare these two:

Code:

//# (1) The declaration of `foo'
extern const u8 foo[];


//# (2) The definition of `foo'
const u8 foo[256]= { 1, 2, 3 ... };

The second item is the definition of an array called foo. This is where the actual content of foo is located. The first thing, the one with extern is known as a declaration. It just says that something called foo exists and that it's a const u8 array, but it's not where the actual data for foo is. The 'definition' is the real thing; the 'declaration' is just a name tag.

That you're required to declare variables (and functions) is just part of the language. Whenever the compiler encounters an identifier, it needs to know how to use it; whether it's a variable, or function or macro; what its type is and, for functions, what its arguments are. This method may seem annoying, but it does mean that you can catch certain errors with more ease than if it weren't the case.

#157302 - Polaris - Wed May 21, 2008 10:03 pm

Actually I am quite new to programming, but it is surprising all the things you can with just, arrays, for loops, if's and some basic concepts on OOP and design patterns. Currently I am fascinated with bitwise operations.

LibFat is a file system? You don't say?! I really need to pay more attention when I read things. Because I have seen that name a lot and never made the connection.

#157310 - simonjhall - Wed May 21, 2008 10:36 pm

Back on the original question, yeah including .cpp files (ie code) is a frowned upon thing by everyone who's worth their salt. Yet the thing that bugs me is the massive amount of code that I encounter (often done by senior programmers...) that's in .inl files, included through header files.
When you bug the person in question you always hear the same old excuses. Fun ones include "oh but it's inline - that's compiler pixie dust! there's zero cost!" etc. Love it.

This has the knock-on effect mentioned by sajiimori - if you include the header that includes the .inl file all over your project, every time you change some actual code (not necessarily prototypes) you've got to re-build your project, which can be a right pain in the arse!

Compiler pixie dust has just cost you a half-hour build ;-)

(can you tell I wrote the header dependency scanner/makefile builder where I work?!)
_________________
Big thanks to everyone who donated for Quake2

#157311 - silent_code - Wed May 21, 2008 10:36 pm

<lol> ;^)
no problem!
libfat is not a file system itself, but it is a library that adds support for the fat file system (i guess both 16 and 32, but i don't know and i won't look it up right now ;^D ) to your program.
there's also nitro fs, efs, gbfs and maybe others, each with a set of advantages and disadvantages.

usually, libfat looks like a good choise, but efs (which is one of libfat's derivates, iirc) has come to my attention, too and has a 2.0 release coming. ;^)

ps @ simon: i'm with you man! the horror!
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#157314 - Cearn - Wed May 21, 2008 11:01 pm

simonjhall wrote:
Compiler pixie dust has just cost you a half-hour build ;-)

Because somebody has to say it: [Images not permitted - Click here to view it]

#157316 - tepples - Wed May 21, 2008 11:34 pm

Cearn wrote:
compare these two:

Code:

//# (1) The declaration of `foo'
extern const u8 foo[];


//# (2) The definition of `foo'
const u8 foo[256]= { 1, 2, 3 ... };

It's also fine to declare 'foo' like this:
Code:
extern const u8 foo[256];

This is similar to (1) above, but it lets operator sizeof work properly.

Polaris wrote:
LibFat is a file system? You don't say?!

See FAT@Wikipedia.

Cearn wrote:
[xkcd panel about slacking while code is compiling]

Where I work, our internal apps don't need to be "compiled" to run, but we do have to wait for batch processing to complete, either on our machines or on a business partner's machines. Sometimes I can slack off while that happens; other times I need to send it to the background and get other work done.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#157324 - sajiimori - Thu May 22, 2008 12:24 am

If you have headers including each other, you've got bigger problems than deciding between #pragma once and include guards.

The only reason to not use #pragma once is portability, though every compiler I've used supports it. I thought our compiler at work had a broken implementation, but it turns out that it's fine as long as you don't enable it globally.

#157325 - silent_code - Thu May 22, 2008 12:41 am

@ sajiimori: ... which sounds hacky... ;^D
but you're right, *that* should not be a *real* problem. most of the time, such "problems" simply indicate much bigger issues in one's design or project management.
_________________
July 5th 08: "Volumetric Shadow Demo" 1.6.0 (final) source released
June 5th 08: "Zombie NDS" WIP released!
It's all on my page, just click WWW below.

#157338 - simonjhall - Thu May 22, 2008 7:47 am

One of the targets that I write for supports pragma once (it's a gcc) yet every time it sees one it gives you a warning! And there doesn't seem to be a way to turn that warning off (bar -Wnone). So I got everyone to change their code ;-)
_________________
Big thanks to everyone who donated for Quake2