5

I'm new to C++ but I do have some experience in Java. While coding, I stumbled across an error that confused me. Here's my code (simplified, but the errors are the same):

A.h:

#pragma once
#include "B.h"
class A
{
public:
    A();
    void foo();
    void sayHello();
    B b;
 };

A.cpp:

#include "A.h"
#include <iostream>    
A::A() {}
void A::foo() {
    b.bar(this);
}
void A::sayHello() {
    std::cout << "Hello" << std::endl;
}

B.h:

#pragma once
#include "A.h"
class B
{
public:
    B();
    void bar(A *a);
};

B.cpp:

#include "B.h"
B::B(){}
void B::bar(A *a) {
    a->sayHello();
}

I want to pass a pointer of the a object to the bar function in B, so that I'll be able to modify and access a's fields in bar. Strangely, I get these errors when I call foo through an instance of A from another class:

1>------ Build started: Project: Test, Configuration: Debug Win32 ------
1>  main.cpp
1>d:\stuff\visual studio 2015\projects\test\test\b.h(7): error C2061: syntax error: identifier 'A'
1>  B.cpp
1>d:\stuff\visual studio 2015\projects\test\test\a.h(9): error C3646: 'b': unknown override specifier
1>d:\stuff\visual studio 2015\projects\test\test\a.h(9): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>  A.cpp
1>d:\stuff\visual studio 2015\projects\test\test\b.h(7): error C2061: syntax error: identifier 'A'
1>d:\stuff\visual studio 2015\projects\test\test\a.cpp(5): error C2660: 'B::bar': function does not take 1 arguments
1>  Generating Code...
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

The code works fine if I don't include A.h in B.h and I don't pass anything to the bar function.

I tried to google what could cause these errors but I wasn't able to resolve the issue by myself becuase I don't understand what's causing these errors. What am I doing wrong?

lux
  • 139
  • 2
  • 8
  • look for for `cyclic includes` and `forward declaration` – Thomas Mar 31 '16 at 23:26
  • Circular refference. More here:http://stackoverflow.com/questions/625799/resolve-circular-dependencies-in-c and here: http://stackoverflow.com/questions/17865286/c-circular-include and here: https://en.wikipedia.org/wiki/Circular_dependency . TL;DR: one of the headers has to be included first and because it includes the other, the other can't include the first to get the definitions it needs because the first has already been included. – user4581301 Mar 31 '16 at 23:26

4 Answers4

2

In the header file of A.h, you have:

#include "B.h"

In the header file of B.h, you have:

#include "A.h"

You have a circular include where the definition of A and B depends on each other.


A simple solution is to use forward declaration in definition of class B:

  1. Replace the line #include "A.h" in file B.h with class B;
  2. Add #include "A.h" at the beginning of B.cpp

Notice however, you cannot use forward declaration for class A, the reason is that you have a value b of class B as a member variable of class A:

#pragma once
#include "B.h"
class A
{
public:
    A();
    void foo();
    void sayHello();
    B b;             /// I mean this line here more specifically
 };

The compiler need to know the the definition of class B in order to determine the correct size for class A. That's why you have to put the #include "B.h" at the very beginning of A.h.

Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • I never thought to just declare one of the classes like that at the top of the file instead of declaring it as a class in the function definition. It looks a little cleaner than what I came up with since it doesn't need the definition for every time it is used. – David Rector Mar 31 '16 at 23:45
  • Thanks! This worked for me. I didn't know about forward declaration yet :) – lux Mar 31 '16 at 23:58
2

Let's take a look at B.cpp and watch what happens with the includes:

#include "B.h"

B::B(){}
void B::bar(A *a) {
    a->sayHello();
}

With B.h pasted in place of the include we get:

#include "A.h"

class B
{
public:
    B();
    void bar(A *a);
};


B::B(){}
void B::bar(A *a) {
    a->sayHello();
}

And then placing A.h in place of its include:

#include "B.h"
class A
{
public:
    A();
    void foo();
    void sayHello();
    B b;
 };

class B
{
public:
    B();
    void bar(A *a);
};


B::B(){}
void B::bar(A *a) {
    a->sayHello();
}

And here we stop because pragma once prevents re-inclusion of B.h

We can see that in class A's definition we have B b;, but class B is not yet defined. Kaboom. A cannot be defined without B and B is not defined yet.

A must have the size of B to satisfy B b;, so it requires the full definition of B. B only needs to know A exists because it only needs a pointer to A to satisfy void bar(A *a);. So...

A.h

#pragma once
#include "B.h"
class A
{
public:
    A();
    void foo();
    void sayHello();
    B b;
};

A.cpp

#include "A.h"
#include <iostream>    
A::A() {}
void A::foo() {
    b.bar(this);
}
void A::sayHello() {
    std::cout << "Hello" << std::endl;
}

B.h

#pragma once

class A; // forward definition of class A
class B
{
public:
    B();
    void bar(A *a);
};

B.cpp

#include "A.h"
B::B(){}
void B::bar(A *a) {
    a->sayHello();
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
  • Thanks for the detailed explaination of why that's happening. I'm understanding it better now :) – lux Apr 01 '16 at 00:04
  • @mrlux Thanks for giving me a chance to write this answer with short-but-meaningful headers. Well-composed question, by the way. – user4581301 Apr 01 '16 at 00:12
1

Both headers are referencing each other. Only one can ever actually be evaluated first by the compiler and that one cannot resolve the reference to the class in the other header. It's not obvious by the error, but the "once" #pragma is making one of the header includes not happen as you expect.

If the B.h header file does not need to know implementation details about the A class, which it does not in this case, then do not include the A.h file in B.h. Instead, change your function declaration of bar() to look like this:

bar( class A *a );

The compiler can build code from this that passes a pointer to an A object without knowing anything about what is inside of A. It doesn't need the A.h header.

Then include the A.h header file after the B.h header file in B.cpp.

I tested this and it works for me.

David Rector
  • 958
  • 9
  • 31
  • It worked for me to use a forward declaration of A at the beginning of B.h. What's exactly the difference between that and declaring bar like you did? Which is best practise? – lux Apr 01 '16 at 00:01
  • I don't think that there is a meaningful difference between what I did above and the other suggestions. Their way is better if you want to avoid the extra typing for more than one of these. – David Rector Apr 01 '16 at 05:03
0

Do these changes:

B.h: - Forward declare A

#pragma once
// #include "A.h"
class A;
//^^^^^^

class B {
public:
    B();
    void bar(A *a);
};

B.cpp: #include "A.h"

//#include "B.h"
#include "A.h"
//^^^^^^^^^^^^

B::B(){}
void B::bar(A *a) {
    a->sayHello();
}

That's it.

Andreas DM
  • 10,685
  • 6
  • 35
  • 62