1

Could someone explain why I receive a syntax error: Identifier 'Bar' during building without the inclusion of class Bar; in the foo.hpp header file?

I don't receive any errors in Visual Studio 2019 before building, and the build order appears to be bar, then foo, then main, so following the #include statements it would seem as though the bar header file is first included inside the foo header during the build.

I have included code below outlining the basic problem.

//Foo header file
#pragma once

#include "bar.hpp"  
#include <iostream>

class Bar; //Commenting this line out results in no longer being able to build the project

class Foo {

public:
    Foo();

    void pickSomething(Bar& bar);
};

//Foo cpp file
#include "foo.hpp"

Foo::Foo() {
    std::cout << "Made Foo" << std::endl;
}

void Foo::pickSomething(Bar& bar) {
    bar.getSomething();
    std::cout << "Picked something!" << std::endl;
}

//Bar header file
#pragma once

#include "foo.hpp"
#include <iostream>

class Foo;

class Bar {

public:
    Bar(Foo& foo);

    void getSomething();
};

//Bar cpp file
#include "bar.hpp"

Bar::Bar(Foo& foo) {
    std::cout << "Made bar" << std::endl;
}

void Bar::getSomething() {
    std::cout << "Gave something!" << std::endl;
}

//main file
#include "foo.hpp"
#include "bar.hpp"

int main() {
    Foo foo;
    Bar bar(foo);
    foo.pickSomething(bar);

    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Crook
  • 733
  • 1
  • 6
  • 9
  • do you receive those errors during build time or they are highlighted in the code sidebar? – mangusta Nov 03 '19 at 05:20
  • I only receive them during build time. – Crook Nov 03 '19 at 05:20
  • 2
    It appears you have a circular include. file1 can't include file2 if file2 includes file1. – Retired Ninja Nov 03 '19 at 05:23
  • Say the two classes depend on one another in the manner of the original code, what would be the proper way to organize my files? Should foo and bar be defined in a single header? Somehow that feels like poor practice. – Crook Nov 03 '19 at 05:29
  • 1
    See [this answer](https://stackoverflow.com/a/5320021/5231607) for an explanation of a very similar case. – 1201ProgramAlarm Nov 03 '19 at 05:43

2 Answers2

1

Summary:

  • foo.hpp includes bar.hpp and bar.hpp includes foo.hpp. This is a cyclic dependency.
  • #pragma once makes sure to not to load the same header again, if it is already loaded.

  • Only the compilation of bar.cpp fails (foo.cpp and main.cpp will be successfully compiled)

Let's follow the pre-processor when compiling bar.cpp. Things happen in the following order.

  1. bar.cpp includes bar.hpp
  2. bar.hpp includes foo.hpp. (Note the following two points)
    • Pre-processor remembers it entered bar.hpp and will avoid entering it again in current cycle.
    • The symbol Bar (declaration of class Bar) is not still loaded as foo.hpp is included before that
  3. foo.hpp try to include bar.hpp
    • Pre-processor knows it already entered bar.hpp, hence this is ignored !
    • However, Foo class declaration uses a symbol called Bar. But this is not declared before !

The unknown symbol Bar could have represented many things (a class, a macro etc..). So the compiler (rightfully) fails as it doesn't know how to treat it.

The solution is forward declaration. There, you are guaranteeing the compiler that the symbol Bar is representing a class. As long as you are not doing anything where the compiler need to know about the class layout (e.g. define a member function of type Bar, access members of Bar etc...) the compilation will succeed.

Anubis
  • 6,995
  • 14
  • 56
  • 87
  • Out of curiosity, if the compiler did need to know about the class layout, what might be the best remedy then? – Crook Nov 03 '19 at 07:18
  • IMO, that needs to be handled by the design level as it probably signals a design problem. But mind that if you use pointer members, forward declaration is enough. You only need the full definition in source files when de-referencing the pointer. So usually such problems can be easily solved this way. – Anubis Nov 03 '19 at 07:31
0

foo.hpp and bar.hpp do not depend on each other, so forward declarations are a good way to avoid a circular dependency between their header files. Only the .cpp files actually need to include both header files.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436