Circular References in Header Files


#1

Sorry for posting a general c++ question here but this forum seems to have become my primary source for programming help. :smiley: I have 2 classes that need to store pointers of each other. I tried to simply include the other class in each of the headers but that doesn’t seem to work. I Then tried forward declarations and they did work. My question is when should I include headers and when should I use forward declarations… Should I use forward declarations in the header files and includes in the cpp file? Also, the compiler reports the same errors multiple times for header files despite using the #ifndef #define #endif multiple inclusion protection.

Thanks,
Caleb


#2

If you’re defining a class, but only declaring its member functions, then you can easily add a pointer to a different class. You don’t need to know the structure of a class to be able to hold a pointer to it - it’s only when you try to dereference it, or use it to call a member function of that class, that you find a problem.

So, the solution is always to separate the implementation of your functions (specifically any that make use of the pointer) from the class definition.

you obviously know your stuff and so i apologise if the rest of this sounds really elementary and childish! but examples are fun!

For example, in my class I know that i need to make use of a gwargle… i even know that a particular function will take/return a gwargle… but right now i don’t know what a gwargle is, and that’s okay because i don’t yet know what those functions will try to do with it. As long as i know that a gwargle exists (forward declaration) and i don’t try to do anything with it, i’m okay. The same goes for the gwargle… he is expecting to have to do something with me at some point… he knows he’s got to bear me in mind, but until he goes off to compile his plans, he doesn’t need to know what i actually am. And by the time he’s got back, he’ll have been given a blueprint showing exactly what he needs to know about me. Heck, i’ve just started learning my ‘gwargle’ functions and i’ve got here a gwargle schematic so i know which bits do what.

So a ‘simple’ solution is to make sure that both classes have their classes defined each in a separate header, each keeping their member function definitions in an external implementation file. The class definitions in the headers are each preceded with a forward declaration of the other, and both implementation files include both headers.

Here’s another simplistic example. [Don’t worry about reading all the code, the important bits are commented and obvious…]

Most basic example, with obvious errors:


class A
{
public:
   int a;
   B* b_ptr;     // <--- first error

   void f()
   {
      b_ptr->b = a;
   }
};

class B
{
public:
   int b;
   A* a_ptr;
   
   void f()
   {
      a_ptr->a = b;
   }
};

Here, class A gives an error, trying to make a pointer to a ‘B’ (which the compiler does not yet know about)… a forward declaration of class B enables the compiler to know enough to allow a pointer to exist…


class B; // forward declaration...

class A
{
public:
   int a;
   B* b_ptr;  // <--- this is okay now...

   void f()
   {
      b_ptr->b = a;  // <--- but THIS is not okay
   }
};

class B
{
public:
   int b;
   A* a_ptr;
   
   void f()
   {
      a_ptr->a = b;
   }
};

… but now the key problem is that the compiler still doesn’t know how to use the pointer- it certainly doesn’t know that there is a ‘b’ int member in this ‘B’ class type.

So we take the function definition outside, away from the class definition, leaving just a declaration…


class B; // forward declaration...

class A
{
public:
   int a;
   B* b_ptr;  

   void f();   // <-- the definition would be in A.cpp
};

class B
{
public:
   int b;
   A* a_ptr;
   
   void f()
   {
      a_ptr->a = b;
   }
};

Everything else in this example is now fine. Of course, we can take it further by putting B into its own file.



// A.h
//----------------------
class B; // forward declaration...

class A
{
public:
   int a;
   B* b_ptr;  

   void f();   // <-- the definition would be in A.cpp
};


// B.h
//----------------------
class A; // forward declaration...

class B
{
public:
   int b;
   A* a_ptr;  

   void f();   // <-- the definition would be in B.cpp
};
// A.cpp
//----------------------



void A::f()
{
   b_ptr->b = a;   
}
// B.cpp
//----------------------



void B::f()
{
   a_ptr->a = b;   
}

#3

sorry that was such a “21 days!” answer :hihi: and sorry if it’s talking about way more obvious things than you asked for!

i think i answered your question somewhere in there tho! that’s what i’d do anyway.


#4

:lol: yup it’s in there, thanx! better more information than not enough.


#5

The amount of times I’ve done a bloody great class all inline stylee then fucking decided it’s goning to need a circular! Grrrrr!!

Where’s the “re-implement inline functions using impl file” button anyway?


#6

yeah it’s a pain in the b’txz0rsz

i use MSVC++Express, and when i find that i need to strip out the implementation to a .cpp file, it’s not usually too troublesome…

  • copy all the inlined functions from the .h to the new empty .cpp

  • in the .h, set outlining to ‘collapse to definitions’, so that the function definitions show up as little […] blocks after the headers.

  • put the cursor on the first function header, and then use the following key sequence…
    [END] [BACKSPACE] [ ; ] [DOWNARROW]
    … that will move to the end of the row, delete the whole function block, put a statement closing semicolon on the end, and move to the next line. keep doing that (pressing DOWN several times if there are gaps between the rows) and your functions have been stripped down.

  • in the .cpp file, you just need to fill your clipboard with a “ClassName::” to paste in front of all the function names. i usually just copy the constructor name, paste it infront of itself and put in the ::, then copy that whole first bit. Then it’s just a case of using the mouse and CTRL-V … click just before the function name, ctrl-v, etc…

  • then, all that’s left is to add in the .cpp file is the relevant #include to get the class interface, and also delete any default value assignments in your function headers, as they only need to show in the header file.

of course everyone knows how to do that, but there are my ‘speedy’ tips on how to try to not kill yourself when you find yourself in that situation.


#7