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++ > C++ file organization

#18347 - poslundc - Wed Mar 24, 2004 6:30 pm

I've used C++ for a number of projects but never for anything requiring more than a few files. I was wondering what the pitfalls of file organization in C++ are as opposed to C.

In C I would never put any executable code in a header file, but class definitions seem a bit more ambiguous from the C++ point of view.

What I want to do is similar to what I would do in C or Objective C, but I don't know if it's correct. That is:

- I would put the class structure - including its members and method prototypes (and code for inline methods as well, I suppose) - in a header file which could then be #included by any other C or C++ file that accesses the object.

- I would then put the actual method routines in a separate .cpp file devoted specifically for that class, which could then be linked to.

Is this the correct way to do things in practice? On the Internet, many of the examples I see place code directly in the class definition, especially in the case of things like constructors and operators. Is this kosher, or is it considered bad programming? What is the structured way to do this stuff in C++?

Thanks,

Dan.

#18348 - yaustar - Wed Mar 24, 2004 6:42 pm

The way I have been taught is to do put structs, function prototypes in te header file and variables into private...but thinking about it, is this correct. It is against the what I learnt here about data into headers...ah well....here is an example

header
Code:
//----------------------------------//
//Author: Steven Yau            //
//Updated: 03/03/2003            //
//Reference: www.gametutorials.com   //
//----------------------------------//

//----------------------------------//

struct NODE
{
   int data;                  //This is our data

   NODE *next;                  //This is a pointer to "our next node" in our linked list
   NODE *prev;                  //This is a pointer to "our previous node" in the linked list
};

//----------------------------------//

class lList 
{
public:
   lList(void);               //Constructor set pointers to NULL
   virtual ~lList(void);         //Destructor, frees all memory
   void insert(int newData);      //Inesrts a node into into list before current NODE
   void deltree(void);            //Deletes a node
   int get(void);               //Returns data from NODE 'nodeNO'
   void up(void);               //Goes up a node
   void down(void);            //Goes down a node
   int size(void);               //Returns the size of the list

   //Functions that were used in this development
   int whatNode(void);            //Returns currentNode
   void pointers(void);         //Returns the memory address of 'prev' and 'next'

//----------------------------------//

private:
   NODE *begin;               //The first node
   NODE *end;                  //The last of the list *will always be empty?*
   NODE *currentNode;            //What NODE are we at. Points to current NODE

   //Variables used for developing for this program
   int currentNodeNo;
};

//----------------------------------//

//end code

cpp
Code:
//----------------------------------//
//Author: Steven Yau            //
//Updated: 03/03/2003            //
//Reference: www.gametutorials.com   //
//----------------------------------//

#include <stdlib.h>
#include <iostream.h>
#include "lList.h"

//----------------------------------//

lList::lList(void){
   begin = NULL;               //Linked list must have a beginning, this is a
                           //simple way of declearing it is empty ('NULL'
                           //needs the "#include <stdlib.h>"
   end = NULL;               
   currentNode = begin;         //Shows us what node we are currently at

   currentNodeNo = 1;
}

//----------------------------------//

lList::~lList(void){
   NODE *walker = begin;         //Set a "walker node" to the front of the list

   while(walker != NULL)
   {
      NODE *temp = walker;      //Create a temp node
      walker = walker->next;      //Move the walker to the prevoius node on the list
      delete temp;            //Free the memory (note the keyword 'delete', this
                           //deallocate memory
   }

   begin = end = NULL;            //Resets begin and end to NULL
}

//----------------------------------//

void lList::insert(int newData){   //Arraghh! Cannot insert before 'begin' yet...
   if(begin == NULL){            //If the list NODE is empty
      //All this is basically initialisation for when data is inserted
      //This will be changed in a later version
      begin = new NODE;         //Allocate memory to 'begin'
      begin->data = newData;
      begin->prev = NULL;

      end = new NODE;            //Allocate memory to 'end'
      end->data = 20;
      end->next = NULL;         
      end->prev = begin;         //Prev points to prevoius NODE

      begin->next = end;

      currentNode = begin;
   }

   //Too many flipping pointers!!!

   else{if(currentNode != begin){
   NODE *node = new NODE;         //Allocate memory for an empty node
   
   //Making sure all the pointers are pointers are pointing to the right place.
   //This is very hard to follow, best use a piece of papar and pen to keep track
   //of this (I had to :p).
   node->data = newData;
   node->next = currentNode;
   node->prev = currentNode->prev;

   currentNode->prev = node;
   currentNode = node->prev;

   currentNode->next = node;
   currentNode = node;

   begin->prev = NULL;
   end->next = NULL;
      }   
   }      
}

etc

//end code

//functions coded:   insert()
//               get()
//               deltree()
//               size()
//can insert and delete data BETWEEN the 'begin' and the 'end' at present

_________________
[Blog] [Portfolio]

#18352 - sajiimori - Wed Mar 24, 2004 7:35 pm

The method you were describing is pretty standard.

If the internet examples you were mentioning have some inlined methods, it's normal to put those in the header. If they were putting non-inlined methods in headers, that's a bad thing as usual.

Another guideline I've heard is to never include headers from headers, but instead force the user to include the headers that are required beforehand. I'm not sure I agree with that one. Maybe one benefit is that you don't have to put #ifdef/#endif in every header.

#18355 - poslundc - Wed Mar 24, 2004 9:21 pm

sajiimori wrote:
Another guideline I've heard is to never include headers from headers, but instead force the user to include the headers that are required beforehand. I'm not sure I agree with that one. Maybe one benefit is that you don't have to put #ifdef/#endif in every header.


That seems impractical. What if you are subclassing another class or (to reduce it to a more generic C case) you are building a module that uses another module and the data structures from that module?

Dan.

#18359 - sajiimori - Wed Mar 24, 2004 10:47 pm

I guess the idea is to document the requirement and make people include the base class header first. But I agree that this is annoying.

One habit I did get into was forward-declaring classes instead of including their headers, when possible. This reduces compile times without messing with pimpl.
Code:

class Widget;

class Blah
{
 public:
  void FrobWidget(Widget* w);
};

#18362 - bomberman - Wed Mar 24, 2004 11:53 pm

200% agree with sajimori...

People tend to #include whenever they need a class or struct. Very bad habit ! On big projects, it kills your compile time. So always try to use pointers or references, this way you can use the forward declaration feature. Otherwise, you need to #include.

#18367 - LOst? - Thu Mar 25, 2004 12:41 am

bomberman wrote:
200% agree with sajimori...

People tend to #include whenever they need a class or struct. Very bad habit ! On big projects, it kills your compile time. So always try to use pointers or references, this way you can use the forward declaration feature. Otherwise, you need to #include.


I have never forward-declarated any classes before. Even though it takes less time to compile, I think it's wrong because you have no idea where the class is declared if you can't search inside files for it.

As long as you arrange the classes the basic style, I think it's pretty much okay coded. Though I think it's a pain in the ass to create classes inside header files and then have a CPP version where you put all it's methods, but it's the right way because then you can use a library, which I never do anyway.

#18375 - Andy Friesen - Thu Mar 25, 2004 6:19 am

Quote:
Another guideline I've heard is to never include headers from headers, but instead force the user to include the headers that are required beforehand.


ew. NO.

if X.cpp needs class Y, it should include Y.h. It should not care one whit about what Y depends on. This violates the underlying idea of what exactly encapsulation means, not to mention the simple fact that it quickly becomes an irritation.

In general, you want to prototype instead of #including everywhere you can, but if #include is necessary, so be it.

Quote:
Maybe one benefit is that you don't have to put #ifdef/#endif in every header.


Maybe, but I hardly think it's a worthwhile tradeoff to have to include umpteen headers just to use one class. Further, when you have a sea of includes at the top of a source file like that, you're more likely to redundantly include some header or other than ever.

Back to the initial topic of the post, the general rule is that declarations go in the header and definitions go in the source file. There are two exceptions to this: things you want (need) to be inlined and templates must both be declared in the header.
_________________
"Tell a man there are 300 billion stars in the universe and he'll believe you. Tell him a bench has wet paint on it and he'll have to touch it to be sure."

#18376 - sajiimori - Thu Mar 25, 2004 7:22 am

You're preaching to the choir, fella. ;-)

#18378 - OptiRoc - Thu Mar 25, 2004 8:23 am

poslundc got it all right from the start -- per usual it seems. :)

The main (only?) difference from C would be that actual code can, and should be, written in header files for inlines and templates.

#18386 - bomberman - Thu Mar 25, 2004 11:55 am

Quote:

I have never forward-declarated any classes before. Even though it takes less time to compile, I think it's wrong because you have no idea where the class is declared if you can't search inside files for it.

As long as you arrange the classes the basic style, I think it's pretty much okay coded. Though I think it's a pain in the ass to create classes inside header files and then have a CPP version where you put all it's methods, but it's the right way because then you can use a library, which I never do anyway.


As long as your project consists in one file, yes you do not need headers :O) But let's be serious, you won't go very far like this !
Why is it a pain in the ass ? This is clean. In headers you expose everything you provide to other projects. You HAVE to separate the interface and the implementation.

Reasons for forward declaration:
- knowing where the class is declared is not essential.You do not want to know where the class is declared, you want to use it ! You must think in terms of concepts, not in terms of implementation. If you move your file (which can happen often during the life of your project), then you have to modify all the headers depending on it.
- if you really want to know where the class is declared, all decent compilers or IDEs give you a browser. You right click on your class and then you are transported to declaration/implementation/use case, which is even better. Personnally, I do not use browsers, I use my memory :)
- If your files are organized in an intelligent way, (nice file hierarchy, namespaces), even without a browser or without memory, it should be easy to find your class. If it is not the case, maybe it's time to rethink it.
- when using things like STL or CORBA, which are heavily templated, if you #include everything, your time compile will be 200% or 300% longer. This IS a fact we have experienced !

#18400 - torne - Thu Mar 25, 2004 3:25 pm

Surely the easiest way to find your classes while forward-declaring them is to name your files after the classes they contain? =)

#18405 - Sweex - Thu Mar 25, 2004 4:52 pm

Indeed, and unless classes are really closely related, put each class in a seperate .h + .cpp!

Also, try to keep the number of lines limited, let's say no more than 2000 lines.

(Currently I'm browsing through files of up to 13000 lines and it's HORRIBLE! Find in Files is my main "tool" nowadays!:-( )
_________________
If everything fails, read the manual: If even that fails, post on forum!

#18414 - sajiimori - Thu Mar 25, 2004 8:09 pm

That's one thing I noticed when switching to Lisp -- big files are much more practical, given a good editor.

Just looking at my old C++ project folders gives me a headache. Giving each class it's own pair of .cpp/.h files is probably the right thing to do, but it only makes the best of a bad situation.

I always found that I was discouraged from making new abstractions (even when I thought it would lead to superior design) because the costs were too great -- 2 more project files, and a bunch of useless "ifdef endif include class public virtual void const" scaffolding that has nothing to do with the task at hand.

#18423 - poslundc - Thu Mar 25, 2004 8:53 pm

sajiimori wrote:
I always found that I was discouraged from making new abstractions (even when I thought it would lead to superior design) because the costs were too great -- 2 more project files, and a bunch of useless "ifdef endif include class public virtual void const" scaffolding that has nothing to do with the task at hand.


I suppose one solution would be an IDE that can template/automate most of this stuff for you.

Personally, I like having to do all that stuff. I find it gives me a chance to think through my implementation while I'm actually doing it, but before I start doing any hardcore coding. Inflicting a little busywork on yourself can do wonders for the quality of the code you write. YMMV, naturally.

Dan.