0

I'm trying to separate the following two classes so each class can be defined in its own Header:


#include <iostream>

class Boo;
class Foo
{
public:
    Foo(Boo *booPtr)
    :booPtr(booPtr){};
    virtual ~Foo(){};
    Boo *booPtr;
};

class Boo
{
public:
    Boo()
    :foo(this){};
    virtual ~Boo(){};
    Foo foo;
    int num = 32;
};

int main()
{
    Boo *boo = new Boo;
    std::cout << "booPtr : " << boo->foo.booPtr->num << '\n';
    delete boo;
}

The Result:

booPtr : 32
Program ended with exit code: 0

And here is my failed attempt at separation:

"Foo.hpp"

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>
#include "Boo.hpp"

class Foo
{
public:
    Foo(Boo *booPtr)
    :booPtr(booPtr){};
    virtual ~Foo(){};
    Boo *booPtr;
};

#endif /* Foo_hpp */

"Boo.hpp"

#ifndef Boo_hpp
#define Boo_hpp

#include <stdio.h>
#include "Foo.hpp"

class Boo
{
public:
    Boo()
    :foo(this){};
    virtual ~Boo(){};
    Foo foo; // Error : Field has incomplete type 'Boo'
    int num = 32;
};

#endif /* Boo_hpp */

"main.cpp"

#include <iostream>
#include "Foo.hpp"
#include "Boo.hpp"

int main()
{
    Boo *boo = new Boo;
    std::cout << "booPtr : " << boo->foo.booPtr->num << '\n';
    delete boo;
}

But I can't build the code since it generate's the following error:

Boo.hpp -> Foo foo; -> "Field has incomplete type 'Boo'"

How can I fix my code?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Zack Lee
  • 2,784
  • 6
  • 35
  • 77
  • 2
    1) Your code suffers from circular includes ([related](https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes)) 2) Forward declarations. (as was done in the first attempt). – Algirdas Preidžius Aug 26 '18 at 07:22
  • See here: [What are forward declarations in C++?](https://stackoverflow.com/questions/4757565/what-are-forward-declarations-in-c) – Geezer Aug 26 '18 at 08:01
  • And here: [When can I use a forward declaration?](https://stackoverflow.com/questions/553682/when-can-i-use-a-forward-declaration) – Geezer Aug 26 '18 at 08:01

3 Answers3

2

You have one file that includes the other and vice versa. This creates a cycle of inclusion.

Realize that the #include macro does nothing but to replace itself (that is, it's line) with the other file. This makes it obvious why you can't have file A include file B.

The obvious solution is to put a forward declaration of Boo in Foo:

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>
class Boo;

class Foo
{
public:
...

Strangely, you already did this before you separated them.

Now a little bit more theory for you: A class is technically a data storage. It needs to know it's size in order to reserve memory. Therefore, it needs to know all the sizes of it's members, which it can only know when they are declared. Therefore, a class needs to include the declaring headers of each class it has as a member. However, a pointer to an object is different (same goes for references). A pointer always takes the same size, that is 32 or 64 bit, depending on your platform (probably 64 bit since we have 64 bit platforms nowadays). Therefore, the class does not need to know the class it points at, the memory it reserves for it's pointer member is always of the same size. That's why a forward declaration, that says nothing about a classes size, is fine here.

Aziuth
  • 3,652
  • 3
  • 18
  • 36
  • @Aconcagua Yeah, but I don't want to go too far abroad. Let's stay with some basics. Plus my text is not wrong, it just does not mention references. – Aziuth Aug 26 '18 at 07:38
  • Hiding the other half could give the impression that it's different with references, so I'd still (just) mention them in style of *"a pointer (and a reference as well)"*. If I wanted to be *pedantic*, I *could* complain about pointer sizes, as even today, there are some 16-bit micro controllers out there having 16-bit pointers... – Aconcagua Aug 26 '18 at 08:06
  • @Aconcagua: Kay, since it doesn't hurt, I put that in the answer. But please, realize that taking things simple is key when people learn new stuff. I realize that you are a quite able in c++, most likely quite more experienced than I am, but don't walk into the trap of being too technical towards beginners. – Aziuth Aug 26 '18 at 08:10
  • Totally agree on. But mentioning the references might prevent a *beginner* turning this reference into a pointer just because of (falsely) believing she/he needs to for being able to use pre-declared only types... – Aconcagua Aug 26 '18 at 08:15
2

While circular inclusion by itself, proper include-guards assumed, is not an error, remember it's just textual replacement:

Make sure all the permutations are viable and have the same meaning!

In your case, replace the inclusion of "Boo.hpp" with the forward-declaration, which you used for a reason in the merged source.

Or, you know, simply abandon splitting the header:
Remember that classes are not necessarily the proper unit of organization.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • I mean, I could imagine a system that does not separate classes into own files each, but in most cases, we'd have it that way. Plus, especially to a beginner, I'd heavily recommend to use the one header per class principle. Like a rule of thumb that one only breaks if he has a good reason, like some others we find in programming. – Aziuth Aug 26 '18 at 07:36
  • 1
    @Aziuth The more coupled two pieces of code, the less separating them into different headers makes sense, as it doesn't reduce complexity or compile-time. One of the flawed ideas many programmers, especially newbies, must be saved from is that classes are the most important thing, and naturally the unit of organization. Separating tightly coupled code into multiple files rarely makes any sense, even if many IDE's templates encourage that. – Deduplicator Aug 26 '18 at 07:48
  • I like to read more about that. My own style is pretty much based on classes. Can you recommend me some articles or a blog or something like that? – Aziuth Aug 26 '18 at 07:50
  • In quite a few cases of tightly coupled classes it even is more appropriate to make one a nested class of the other one, e. g. the `Node` class of a linked list. – Aconcagua Aug 26 '18 at 08:22
1

Addendum on circular includes, starting with Foo.hpp:

#ifndef FOO
#define FOO  // (!)

// now including BOO!
#ifndef BOO
#define BOO

// now including FOO  a g a i n
// as FOO  i s  already defined, all that remains is:
#ifndef FOO
#endif

class Boo { }; // now lacking definition of Foo...

#endif // BOO

class Foo { };

#endif // FOO (first one)

Analogously for Boo.hpp (you'd only need a pre-declaration of Foo, but there is none as the definition again follows afterwards...).

As you see, circular includes, appropriate include guards provided, won't result in endless self inclusion. However, the outcome is not the one you intended.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Out of curisity - this pretty much looks like somebody shouldn't ever do. Am I wrong there? Can you give me an example where one would use this? – Aziuth Aug 26 '18 at 07:53
  • 1
    @Aziuth Imagine you have two template classes each one referring the other one. You could then define the classes, but without implementing those functions using the other template, *then* include the other header, *then* provide the implementations. – Aconcagua Aug 26 '18 at 08:01
  • Ah of course, yes. Haven't made that jump for the irregularity that those are, thanks. – Aziuth Aug 26 '18 at 08:03