Jump to content
  • Advertisement
Sign in to follow this  

Multiple Source Files

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

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi. My program is getting quite large and I'd like to split it up into smaller .cpp files but I don't know how to. I've made some attempts but they don't work as I've basically guessed what I need to do. I can find guides telling me about how wonderful it is but not what I actually need to do! If someone could provide me with a guide or some examples that would be great. Basically what do I need to put in the headers, what do I do about global variables (YES I need them) defining functions and so on. Oh and I don't know how to use classes, so it's no good talking about them :P (Well I've come along just fine so far without them) THANKS!!!

Share this post


Link to post
Share on other sites
Advertisement
Yes I have already read that. It does not tell me what I need to do. It assumes I know how to do it already, thus defeating the point of the guide.

Share this post


Link to post
Share on other sites
Are you sure you didn't just read the first page? It's five pages long and explains what to put in header files, what to do about gobal variables, where to define functions, and so on. If you did read all of it perhaps it would be easier for you to quote some part you found to be vague and we can help clear it up.

Share this post


Link to post
Share on other sites
Here is the all-on-one-HTML-page version.

Quote:

Relevant snippets that certainly do tell you what to do and assume very little if anything!
How to do it: The Basics

...

Firstly, look at how you would split your code into sections. Often this is by splitting it into separate subsystems, or 'modules', such as sound, music, graphics, file handling, etc. Create new files with meaningful filenames so that you know at a glance what kind of code is in them. Then move all the code that belongs to that module into that file.

...

Once you have split it up in this way into separate source files, the next stage is to consider what will go into the header files.

This code to go in a header usually includes some or all of the following:

* class and struct definitions
* typedefs
* function prototypes
* global variables (but see below)
* constants
* #defined macros
* #pragma directives

(Additionally, when using C++, templates and inline functions usually need to be in the header file. The reasons for this should become clear later.)

...

You generally want one header file for every source file. That is, a SPRITES.CPP probably needs a SPRITES.H file, a SOUND.CPP needs a SOUND.H, and so on. Keep the naming consistent so that you can instantly tell which header goes with which normal file.

These header files become the interface between your subsystems. By #including a header, you gain access to all the structure definitions, function prototypes, constants etc for that subsystem.

...

Remember that, as far as the compiler is concerned, there is absolutely no difference between a header file and a source file. (Exception: some compilers will refuse to compile a header file directly, assuming you made a mistake in asking.)... The distinction is a conceptual one that programmers must adhere to in order to keep the logical file structure intact. The key idea is that headers contain the interface, and the source files contain the actual implementation.

...

Potential Pitfalls

...

1) The source files no longer compile as they can't find the functions or variables that they need. (This often manifests itself in the form of something similar to "error C2065: 'MyStruct' : undeclared identifier" in Visual C++, although this can produce any number of different error messages depending on exactly what you are trying to reference.)

2) Cyclic dependencies, where headers appear to need to #include each other to work as intended. A Sprite may contain a pointer to the Creature it represents, and a Creature may contain a pointer to the Sprite it uses. No matter how you do this, either Creature or Sprite must be declared first in the code, and that implies that it won't work since the other type isn't declared yet.

3) Duplicate definitions where a class or identifier is included twice in a source file. This is a compile time error and usually arises when multiple header files include one other header file, leading to that header being included twice when you compile a source file that uses them. (In MSVC, this might look something like "error C2011: 'MyStruct' : 'struct' type redefinition.)

4) Duplicate instances of objects within the code that compiled fine. This is a linking error, often difficult to understand. (In MSVC, you might see something like "error LNK2005: "int myGlobal" (?myGlobal@@3HA) already defined in myotherfile.obj".)

...

Fixing Problem 1

Luckily, these issues are easy to fix, and even easier to avoid once you understand them.

The first error, where a source file refuses to compile because one of the identifiers was undeclared, is easy to resolve. Simply #include the file that contains the definition of the identifier you need.

...

Fixing Problem 2

Cyclic (or two-way) dependencies are a common problem in software engineering. Many constructs involve a two-way link of some sort, and this implies that both classes or structures know about each other.

...

Given that one of these has to be compiled first, you need some way to break the cycle. In this case, it's actually quite trivial. The Parent struct doesn't actually need to know the details of the Child class, as it only stores a pointer to one. Pointers are pretty much the same no matter what they point to, therefore you don't need to the definition of the structure or class in order to store a pointer to an instance of that structure or class. So the #include line is not needed. However, simply taking it out will give you an "undeclared identifier" error when it encounters the word 'Child', so you need to let the compiler know that Child is a class or class that you wish to point to. This is done with a forward declaration, taking the form of a class or class definition without a body.

Note: the examples diagrams that follow a bit later are quite useful

...

Fixing Problem 3

Duplicate definitions at compile time imply that a header ended up being included more than once for a given source file. This leads to a class or struct being defined twice, causing an error. The first thing you should do is ensure that, for each source file, you only include the headers that you need. This will aid compilation speed since the compiler is not reading and compiling headers that serve no purpose for this file.

...

Header1.h and Header2.h #include header3.h for some reason... Note that it doesn't matter that header3.h is being #included from different header files: during compilation, these all resolve to one file. The #include directive literally says "include the specified file right here in this file while we process it", so all the headers get dumped inline into your source file before it gets compiled.

...

For the purposes of compilation, File1.cpp ends up containing copies of Header1.h and Header2.h, both of which include their own copies of Header3.h. The resulting file, with all headers expanded inline into your original file, is known as a translation unit. Due to this inline expansion, anything declared in Header3.h is going to appear twice in this translation unit, causing an error.

So, what do you do? You can't do without Header1.h or Header2.h, since you need to access the structures declared within them So you need some way of ensuring that, no matter what, Header3.h is not going to appear twice in your File1.cpp translation unit when it gets compiled. This is where inclusion guards come in.

...

Fixing Problem 4

When the linker comes to create an executable (or library) from your code, it takes all the object (.obj or .o) files, one per translation unit, and puts them together. The linker's main job is to resolve identifiers (basically, variables or functions names) to machine addresses in the file. This is what links the various object files together. The problem arises when the linker finds two or more instances of that identifier in the object files, as then it cannot determine which is the 'correct' one to use. The identifier should be unique to avoid any such ambiguity. So how come the compiler doesn't see an identifier as being duplicated, yet the linker does?

...

Again, useful diagrams and examples.

This first gets compiled into two object files, probably called code1.obj and code2.obj. Remember that a translation unit contains full copies of all the headers included by the file you are compiling. Finally, the object files are combined to produce the final file.

...

Notice how there are two copies of "my_global" in that final block. Although "my_global" was unique for each translation unit (this would be assured by the use of the inclusion guards), combining the object files generated from each translation unit would result in there being more than one instance of my_global in the file. This is flagged as an error... The answer is not to define variables or functions in headers. Instead, you define them in the source files where you can be sure that they will only get compiled once (assuming you don't ever #include any source files, which is a bad idea for exactly this reason). This gives you a new problem: how do you make the functions and variables globally visible if they aren't in a common header any more? How will other files "see" them? The answer is to declare the functions and variables in the header, but not to define them. This lets the compiler know that the function or variable exists, but delegates the act of resolving the address to the linker.

To do this for a variable, you add the keyword 'extern' before its name... and for a function, you just put the function prototype... functions are considered 'extern' by default so it is customary to omit the 'extern' in a function prototype.

...

The rule here is to remember that header files define an interface, not an implementation. They specify which functions, variables, and objects exist, but it is not responsible for creating them. They may say what a struct or class must contain, but it shouldn't actually create any instances of that struct or class. They can specify what parameters a function takes and what it returns, but not how it gets the result. And so on. This is why the list of what can go into a header file earlier in this article is important.

Other Considerations

...

Firstly, if you use the C++ standard library, or any other library that uses namespaces, you may find yourself using their identifiers in your header files. If so, don't use the "using" keyword in your headers... Secondly, the use of macros should be carefully controlled. C programmers have to rely on macros for a lot of functionality, but C++ programmers should avoid them wherever possible.


I don't do this kind of summary every day. Maybe I should, though. :s

Share this post


Link to post
Share on other sites
Thanks but it's all gobbledegook to me.

Consider this simple example (I'm not at my compiler so I don't know if it has any errors in it)

start of main.cpp file
int global = 5;
void show(int var);

int main()
{
show(global);
return 0;
}

void show(int var)
{
if(global == var)
{
cout << "yay";
}
return;
}


how could I split the show function onto another file?

Share this post


Link to post
Share on other sites
Quote:
how could I split the show function onto another file?


show.h

void show(int var);


show.cpp

#include "show.h"

void show(int var)
{
if(global == var)
{
cout << "yay";
}
return;
}



main.cpp


#include "show.h"
int global = 5;

int main()
{
show(global);
return 0;
}

Share this post


Link to post
Share on other sites
While COleException is quite correct, I just thought you might like to see how to put the global int in show.h/.cpp as well. Header guards are a good idea in show.h as well to prevent it being included twice:

show.h

#ifndef SHOW_H
#define SHOW_H

extern int global; // no initialiser here
void show(int var);

#endif





show.cpp

#include "show.h"

#include <iostream>

int global=129213; // initialiser here

void show(int var)
{
if(global == var)
{
std::cout << "yay" << std::endl;
}
return;
}






main.cpp

#include "show.h"

int main()
{
show(global);

return 0;
}






How you then compile and link these two files is dependant on your compiler and IDE. In VSEE, just having them both in the same project does the business. With a command line compiler, you'd probably do something like:

bcc32 main.cpp show.cpp

Unless you were using make or something, then your makefile might look like this:


main.exe : main.obj show.obj
bcc32 main.obj show.obj

main.obj : main.cpp show.h
bcc32 -c main.cpp

show.obj : show.cpp show.h
bcc32 -c show.cpp

Share this post


Link to post
Share on other sites
It appears to be working but I have an enumeration that does not seem to work. Where should I define the enumeration?

enum e_pieces{q=-7, r=-6, b=-5, k=-3, n=-2, p=-1, empty=0, P=1, N=2, K=3, B=5, R=6, Q=7};

e_pieces board[128];

Where should I put this? board needs to be a global variable. It says board indentifer not found even with argument dependant lookup. It thinks it is a function? extern e_pieces board[128]; results in missing ; before board. And I get tons of K undeclared identifier etc and so on. Any advice?

I am using Visual Studio .NET 2003.

Share this post


Link to post
Share on other sites
Put the enumeration in a header file. As long as the header is not included twice, which include guards will prevent, it will be fine.

To declare a global array like that, do this:

file.h
extern e_pieces board[]; // no size or initialisers specified here

file.cpp
e_pieces board[128]={ q,r,b }; // all as normal here

Looks like a chess program then?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!