circular dependencies, unrecognized base types, oh-my

C

crichmon

Any general advice for dealing with circular dependencies? For example, I
have a situation which, when simplified, is similar to:

/////////////
// A.h

class A
{
public:
int x;
};

/////////////
// B.h

#include "A.h"
#include "C.h"

class B: public A
{
public:
C* myC;
};

////////////
// C.h

#include "B.h"

class C
{
public:
B* myB;
};

////////////

The problem that I am having is that if I don't add some kind of forward
declaration in B.h and/or C.h, B and/or C will be undefined when processing
C.h or B.h (respectively). My first question here is in a case like this,
should I put a forward declaration of C in B.h and of B in C.h? Only one is
necessary, but it all depends on which header file the compiler reaches
first.

And now for the compounded problem... I've altered C.h to include a forward
declaration of B after B.h is included, and the first problem (above) goes
away. I have some other code (not simplified and included) in C.h that
explicitly accesses the x integer of B's base class (in C.h: myB->A::x).
The compiler is giving me an error, saying that " 'A' is not a base type for
type 'B' ". Arg! What's going on here?

Now for the disclaimer... I haven't actually tried my simplified example yet
in the compiler, so I don't know if the error behavior I describe can be
produced with it. However it outlines the general problem in my code that
I'm currently dealing with. The only differences is that my classes use
multiple inheritance and templates as well, so those factors could easy add
to the fire...

Anyways, any thoughts or insights into this situation would be greatly
appreciated.

thanks,
crichmon
 
B

Bob Hairgrove

Any general advice for dealing with circular dependencies? For example, I
have a situation which, when simplified, is similar to:
[snip]

(Isn't this a FAQ??)

Use forward declarations for the pointer members and only include the
headers in your .cpp (i.e. implementation) file(s).
 
J

John Harrison

crichmon said:
Any general advice for dealing with circular dependencies? For example, I
have a situation which, when simplified, is similar to:

/////////////
// A.h

class A
{
public:
int x;
};

/////////////
// B.h

#include "A.h"
#include "C.h"

class B: public A
{
public:
C* myC;
};

////////////
// C.h

#include "B.h"

class C
{
public:
B* myB;
};

////////////

The problem that I am having is that if I don't add some kind of forward
declaration in B.h and/or C.h, B and/or C will be undefined when processing
C.h or B.h (respectively). My first question here is in a case like this,
should I put a forward declaration of C in B.h and of B in C.h?
Yes.

Only one is
necessary, but it all depends on which header file the compiler reaches
first.

You can't sensibly control which header file the compiler reaches first. Use
forward decalrations in both cases.
And now for the compounded problem... I've altered C.h to include a forward
declaration of B after B.h is included,

What is the point of having a forward declaration *after* B.h has been
included.
and the first problem (above) goes
away. I have some other code (not simplified and included) in C.h that
explicitly accesses the x integer of B's base class (in C.h: myB->A::x).
The compiler is giving me an error, saying that " 'A' is not a base type for
type 'B' ". Arg! What's going on here?

C.h include B.h, C.h includes A.h, A.h includes C.h. This is a mess isn't
it?

When classes are this interdependent, it's really not a good idea to put
them in seperate header files.

Here's what I suggest, in one header file. I've added void C::func() to
represent the code in C that accesses A::x

class B;
class C;

class A
{
public:
int x;
};

class C
{
public:
void func();
B* myB;
};

class B: public A
{
public:
C* myC;
};

// now A, B and C are fully defined so we can place any code that uses them
here

inline void C::func()
{
myB->x;
}

Simple.

john
 
C

crichmon

John Harrison said:
You can't sensibly control which header file
the compiler reaches first. Use forward
decalrations in both cases.

Okay, thanks!

I figured out the rest of my problem! (see the following):

What is the point of having a forward
declaration *after* B.h has been included.

Because with a circular dependency, it's possible for B or C to not be
defined yet.

Use the examples mentioned previously, but imagine that all are surrounded
by

#ifndef A_H
#define A_H
....
#endif

for file A.h (or B_H for file B.h, and C_H for file C.h).

Let's say that B.h is processed first:

The preprocessor will run "#ifndef B_H" and determine that it's undefined.
It will then define B_H. Next it will include C.h and will begin processing
it.

The preprocessor will run "#ifndef C_H" and determine that it's undefined.
It will then define C_H. Next it will include B.h and will begin processing
it.

The preprocessor will run "#ifndef B_H" and determine that it *is* defined,
and will therefore not do anything inside the ifndef block. Since the
entire file is contained within that block, the preprocessor will return to
processing the C.h file.

The C.h contains the definition for the C class. This definition makes use
of a variable of type "B". Unfortunately because B has not yet been defined
(the preproccessor only got as far as '#include "C.h"' in B.h), the compiler
will complain and exit.

The same scenario would occur if C.h is processed first... the only
difference is that 'C' and 'B' will be swapped in the scenario described.

A forward declaration is necessary, even after the include, so the compiler
knows that the particular types are defined somewhere.

C.h include B.h, C.h includes A.h, A.h
includes C.h. This is a mess isn't it?

When classes are this interdependent,
it's really not a good idea to put them in
seperate header files.

I don't know if I would agree with this, although there are probably valid
arguments for and against putting interdependant classes in the same header.

Here's what I suggest, in one header file.
I've added void C::func() to represent the
code in C that accesses A::x

class B;
class C;

class A
{
public:
int x;
};

class C
{
public:
void func();
B* myB;
};

class B: public A
{
public:
C* myC;
};

// now A, B and C are fully defined so we
// can place any code that uses them here

inline void C::func()
{
myB->x;
}

Simple.

Your solution works because your code for C::func occurs *after* the
definition of the B class. The fact that I didn't realize this right away
is is what gave me difficulty in my program.

Because my previous project had been to learn C#, and since C# combines it's
code and header files into a single *.cs file, I had gotten used to
combining class definition and code into single files. I therefore started
my project with a lot of *.h files that also contained the code for each
function. Therefore, because I had to use forward declarations for B and C
to handle the circular dependency issue, I was left dealing with a forward
declaration of class B that indicated no inheritance (and thus the "'A' is
not a base type for type 'B'" error). So my files looked like this:

/////////////
// A.h

#ifndef A_H
#define A_H

class A
{
public:
int x;
};

#endif

/////////////
// B.h

#ifndef B_H
#define B_H

#include "A.h"
#include "C.h"

class C;

class B: public A
{
public:
C* myC;
};

#endif

////////////
// C.h

#ifndef C_H
#define C_H

#include "B.h"

class B;

class C
{
public:
B* myB;

void func()
{
myB->A::x;
}
};

#endif

////////////

If file B.h is processesd before file C.h, the compiler would complain about
file C.h, saying that "'A' is not a base type for type 'B'" because all it
knew about B was that it was a class due to it's forward declaration.

(My program is actually much more complicated than my example, and I need to
explicitly specific the parent class as I have a situatin where, using this
example, 'func' would call a function defined in template class D, where A
inherits from D<A> and B inherits from D<B>, so not specifying the specific
class would result in an ambiguity error from the compiler.)

So the solution then was to put all code into a seperate code file. Then
everything worked as it should!

crichmon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,231
Members
46,820
Latest member
GilbertoA5

Latest Threads

Top