X has a pointer to Y, and Y has an object of type X

This is a seemingly simple problem from C++ Primer Exercise 12.11

Exercise: Given C++ classes X and Y: X has a pointer to Y, and Y has an object of type X
And (this is my personal requirement!): X and Y in separate files!

(So this is all about forward declaration and incomplete type...)

Currently it does not compile...
How can one get this to compile?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//===== X.h =============================
#ifndef X_h
#define X_h

#include "Y.h"
class Y;

class X
{
 public:
  X();
  int i;
  Y *y;
};

#endif



//===== X.cc =============================
#include "X.h"

X::X() : i(0) {}



//===== Y.h =============================
#ifndef Y_h
#define Y_h

#include "X.h"
class X;

class Y
{
 public:
  Y();
  int i;
  X x;
};

#endif




//===== Y.cc =============================
#include "Y.h"

Y::Y() : i(0) {}



//===== main.cc =============================
#include "Y.h"
#include "X.h"

int main(void)
{
  X x;
  Y y;
  return 0;
}



//===== Makefile =============================
main: main.o Y.o X.o
        g++ $^ -o $@

main.o: main.cc
        g++ -c $<

Y.o: Y.cc Y.h
        g++ -c $<

X.o: X.cc X.h
	g++ -c $<

clean:
      	rm *.o


Thanks for tips!
Last edited on
You won't believe this, but I've found a solution.
But I don't understand it.

The answer is simply to add in the following single line before line 21:
#include "Y.h"

Can someone please explain this? Is this solution good at all, or are there better ways of handling this?
Thanks.
Last edited on
You should be able to remove the include on line 5 and the forward declaration on line 32.
Wow, indeed (in that case one does NOT even have to insert #include "Y.h" before line 21).

Is there a good way of understanding this? How can one conceptualise this into an understandable "coding-style" or guideline?
Thanks.
Last edited on
http://cplusplus.com/forum/articles/10627/#msg49679

^ Read section 4.

As for why including Y.h works, what's happening is this:

main.cpp) includes y.h
y.h) includes x.h
x.h) x.h includes y.h (skipped because y.h was already included)
x.h) X class fully defined
y.h) Y class fully defined


All x.h needs is a forward declaration. It should not be #including y.h... at least not before class X is fully defined.
Well, this explanation can get rather long but I'll give it a shot (trying not to get side-tracked into all possible cases). I'll start at the beginning, too.

First, the compiler compiles a translation unit into an object file. You can see this in your makefile--each object file depends on a source file and a header file1. This separation makes the distinction between declarations and definitions2. Note that the source file must include3 the header file.

In order to use a user-defined type (class) in a translation unit you have to have it's definition (from the header). In order to build the executable, it also needs to have the object file that contains the method definitions. Those are linked in after compilation from the object files.

Now, if you aren't going to "use" the type but rather just hold a reference or pointer to it, you don't need the definition--you only need the declaration, that says it exists. So, in order to store a pointer to a Y in class X, you (at minimum) must provide the forward declaration.

Contrastingly, if you are going to use the type by storing an object, like X in class Y, the compiler needs to know the definition. That requires the header inclusion.

Now, to avoid ambiguity, the executable must also adhere to the One Definition Rule (ODR). Since headers can be included all over the place, inclusion guards are required to prevent multiple definitions (for things defined in headers, such as classes or templates).

I hope you find this explanation is accurate and informative. Let me know if you have any questions!


1 This is called the inclusion model. It's often associated with defining all templates in header files.
2 This terminology is very important. Declarations say there is something that exists and definitions are typically more along the lines of you can use it and/or how it works. It can get confusing because class definitions are quite different from method definitions (I don't want to get into this).
3 Preprocessor includes are replaced with the included files contents verbatim. Think of it as pasting the included file to result in one big text file before compilation.
Last edited on
Thanks so much moorecm!!

I'll need to go over that a few times. ;)

And thanks to Disch, for the good link.

For those who want the clean solution (as given by moorecm above), here it is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//===== X.h =============================
#ifndef X_h
#define X_h


class Y;

class X
{
 public:
  X();
  int i;
  Y *y;
};

#endif



//===== X.cc =============================
#include "X.h"

X::X() : i(0) {}



//===== Y.h =============================
#ifndef Y_h
#define Y_h

#include "X.h"


class Y
{
 public:
  Y();
  int i;
  X x;
};

#endif




//===== Y.cc =============================
#include "Y.h"

Y::Y() : i(0) {}



//===== main.cc =============================
#include "Y.h"
#include "X.h"

int main(void)
{
  X x;
  Y y;
  return 0;
}



//===== Makefile =============================
main: main.o Y.o X.o
        g++ $^ -o $@

main.o: main.cc
        g++ -c $<

Y.o: Y.cc Y.h
        g++ -c $<

X.o: X.cc X.h
	g++ -c $<

clean:
      	rm *.o
Last edited on
Looks good. BTW, the best practice is to always use the minimal approach--forward declare anytime that you can instead of including the header. That will cut down on dependencies, which can speed up compilation time.
Another question I'm hoping someone can answer:

Is it possible to dereference the pointer to Y? See line 11 below.
Can one get that to compile?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//===== X.h =============================
#ifndef X_h
#define X_h

class Y;

class X
{
 public:
  X();
  void dereference_y_pointer() { i = y->i; }  // new line! can this be done? -> currently: compile problems
  int i;
  Y *y;
};

#endif 


Thanks.
Last edited on
I've got a way of dereferencing the pointer to Y, but it only works if I put the code (def) into the X.cc file -> See code below

This works.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//===== X.h =============================
#ifndef X_h
#define X_h


class Y;

class X
{
 public:
  X();
  void dereference_y_pointer(); // just declaration
  int i;
  Y *y;
};

#endif



//===== X.cc =============================
#include "X.h"
#include "Y.h"       /*     this is needed */

X::X() : i(0) {}

void X::dereference_y_pointer() // definition here
{
  i = y->i;
}




//===== Y.h =============================
#ifndef Y_h
#define Y_h

#include "X.h"

class Y
{
 public:
  Y();
  int i;
  X x;
};

#endif




//===== Y.cc =============================
#include "Y.h"

Y::Y() : i(0) {}



//===== main.cc =============================
#include "Y.h"
#include "X.h"

int main(void)
{
  X x;
  Y y;
  return 0;
}



//===== Makefile =============================
main: main.o Y.o X.o
        g++ $^ -o $@

main.o: main.cc
        g++ -c $<

Y.o: Y.cc Y.h
        g++ -c $<

X.o: X.cc X.h
	g++ -c $<

clean:
      	rm *.o


Thanks
Last edited on
In that case you would have to replace the forward declaration with the include. However, what you should prefer (if the method need not be inline) is to put the definition of that method in the source file and include the header there.
I cannot get it to compile by replacing the forward declaration with the include.
And I cannot get it to compile if declaration and definition are (inline) in the header only.

Perhaps in this particular case, the definition must be in the source (X.cc), as is shown in the working code above.
?
Last edited on
Yes, you are correct--it must be in the source file in this case because of the circular dependency. What you have done looks good.
Last edited on
Thanks to moorecm and Disch for your help.

To warp up:
If Y uses class X, then X itself cannot use Y directly
-> Rather: X can then only use pointer to Y or a reference to Y.

In that case the header of X (X.h) needs a forward declaration of Y, i.e. class Y;
See clean solution here: http://www.cplusplus.com/forum/general/46238/#msg251120

If in addition, we dereference the pointer to Y within class X, then this has to be done in the source file of X (X.cc), where we then also need to include the header of Y.
See working solution here: http://www.cplusplus.com/forum/general/46238/#msg251171


It turns out, that one can also dereference the pointer to Y, from the header (as is needed for inline): how to do this can be seen in the following thread: http://www.cplusplus.com/forum/general/47419/
Last edited on
Topic archived. No new replies allowed.