Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: General C++ Header Question [SOLVED] + Demo/Quiz  (Read 1837 times)

0 Members and 1 Guest are viewing this topic.

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
General C++ Header Question [SOLVED] + Demo/Quiz
« on: January 31, 2022, 08:24:21 am »
Hello and good day everyone!

This is a question I'd normally reserve for a place like StackOverflow considering it's a general programming question but I have to wait two days to post again o.o I'd like to solve this error sooner and get on with my life.

I'm having problems with something that has worked for a while, and I'm not sure why exactly it stopped working. I've swapped headers around and done other things to try to satisfy the compiler but it's not having it. This is a common problem I've randomly had with member variable classes and, as soon as I solve them, I forget what I've done to solve the last ones. I'll provide some sample code...

Code: [Select]
#ifndef COMMON_HPP
#define COMMON_HPP

/* Other #include calls omitted for brevity */

#include <Common/Application.hpp>

namespace Game { class TestCase {}; }

class Common {
    public:
        Common();
        virtual ~Common();
        Game::TestCase mTest; //<-- If this is OK...
        Game::Application m_App; //<-- ...then why is that NOT OK...?
};

#endif // COMMON_HPP

I keep getting...

Code: [Select]
error : 'Application' in namespace 'Game' does not name a type.
^Game::Application is defined much in the same way as Game::TestCase, so I'm not sure why I'd get a problem with one and not the other. Something something the chicken and the egg something something..... this shouldn't be that hard.

Moving #include calls doesn't seem to change it. Other member variable classes don't have these issues. I know the compiler needs to see what is what, and load things in a certain order, and profile classes so it knows to allocate enough memory for each class. I've provided the header for the class it needs so I'm not sure the issue. Application holds a bunch of classes, Common holds Application, Application apparently isn't a "type" so... what should I do?

Game::Application is a class in a namespace like Game::TestCase is a class in a namespace. I shouldn't have to do any funky typedef things, right? This seems like an easy to solve problem, I'm surprised I've been stuck on it for two days.

And I tend to sometimes get these errors too. It'd be like...

Code: [Select]
#include <Breakfast/Eggs>
#include <Breakfast/Bacon>
#include <Breakfast/Sausage>

class YoBreakfastPlate {
    Breakfast::Eggs mEggs;
    Breakfast::Bacon mBacon;
    Breakfast::Sausage mSausage;
}

...and the compiler tells me I'm out of Bacon?! That's just not good. The Bacon is sitting right here, I'm looking at it, how am I out of Bacon? Bacon ain't no type of Breakfast, not for you, you get no Bacon! We put no Bacon on YoBreakfastPlate! :o :'(
« Last Edit: February 01, 2022, 01:06:48 pm by Kain Nobel »

kojack

  • Sr. Member
  • ****
  • Posts: 300
  • C++/C# game dev teacher.
    • View Profile
Re: General C++ Header Question
« Reply #1 on: January 31, 2022, 10:06:54 am »
It sounds like there is a circular dependency in there somewhere.
common.hpp is including application.hpp, but what does application.hpp include? Does it include common.hpp or another file that then includes common.hpp? Once you get a couple of includes deep the include guards will stop it going further, but that means something was left without an include it needed.

It's a bit hard to tell exactly where a circular include is happening though without being able to see all of the headers used by the project.

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: General C++ Header Question
« Reply #2 on: January 31, 2022, 12:51:55 pm »
I just went through a major refactoring (the coding equivalent to being a chiropractor, yeouch!) trying to pop things back in place, so bare with me...

I've eliminated the stench @ Game::Application m_App; (within the RGL class header) but now it's popping up at rgl::Graphics m_Graphics; and rgl::Input m_Input; (within the RGL/Application class header). Same type of error, very similar scenario as the (now resolved but useful demonstration) code above. Circular dependencies? I'm trying to iron it out. I don't believe there should be but I'm still new to C++. Still, Ruby has a dependency model (require 'somelib') similar to C++ (#include <somelib.h>). (This project is planned to integrate into Ruby, but that's a non-factor and outside the scope of this topic...)

So here is the newly refactored include calls. It's been changed so class Common is class RGL now, namespace Game is namespace rgl now. I'm not messing with it anymore until tomorrow, so here it is as of right now...

(click to show/hide)

I've been careful to include only what I need, when I need it and where I need it. Each class takes the free floating SFML classes and gives it a well-defined structure for the project I'm setting up. RGL holds rgl::Application (think the application window), rgl::Application holds rgl::Audio, rgl::Graphics, rgl::Input (and maybe rgl::Video in the future) modules. Main calls RGL.main() => (RGL.mainStart, RGL.mainLoop, RGL.mainBreak, RGL.mainEnd) which manages the lifetime of the Application. Pretty much sums up the whole program at the moment.
« Last Edit: January 31, 2022, 01:01:22 pm by Kain Nobel »

kojack

  • Sr. Member
  • ****
  • Posts: 300
  • C++/C# game dev teacher.
    • View Profile
Re: General C++ Header Question
« Reply #3 on: January 31, 2022, 01:50:42 pm »
Ah, yep, there's circular includes in there. For example:
rgl includes application
application includes audio
audio includes rgl
rgl includes application ... and so on.

Here's a simplified example:
// aaa.h
#ifndef AAA_H
#define AAA_H

#include "bbb.h"

class AAA
{
        BBB *b;
};

#endif
 
// bbb.h
#ifndef BBB_H
#define BBB_H

#include "aaa.h"

class BBB
{
        AAA *a;
};

#endif
 
// main.cpp
#include "aaa.h"

int main()
{
   return 0;
}
 

Here's what the preprocessor will do. First, it replaces the #include in main.cpp with the contents of aaa.h:
// main.cpp
#ifndef AAA_H
#define AAA_H

#include "bbb.h"

class AAA
{
        BBB *b;
};

#endif

int main()
{
   return 0;
}

Then it will replace the #include "bbb.h" with the contents of bbb.h:
// main.cpp
#ifndef AAA_H
#define AAA_H

#ifndef BBB_H
#define BBB_H

#include "aaa.h"

class BBB
{
        AAA *a;
};

#endif

class AAA
{
        BBB *b;
};

#endif

int main()
{
   return 0;
}

That added another #include "aaa.h", so it has to replace that too:
// main.cpp
#ifndef AAA_H
#define AAA_H

#ifndef BBB_H
#define BBB_H

#ifndef AAA_H
#define AAA_H

#include "bbb.h"

class AAA
{
        BBB *b;
};

#endif

class BBB
{
        AAA *a;
};

#endif

class AAA
{
        BBB *b;
};

#endif

int main()
{
   return 0;
}

Now this is where it gets stuck. It can't replace the new #include "bbb.h" because that code is disabled by the include guards.
From the top, it checks if AAA_H isn't defined. It isn't, so it defines it.
Then it checks if BBB_H isn't defined. It isn't, so it defines it.
Then it checks if AAA_H isn't defined. But at this point it is (from above), so the entire contents of the third #ifdef block are removed.
// main.cpp
#ifndef AAA_H
#define AAA_H

#ifndef BBB_H
#define BBB_H

#ifndef AAA_H
//#define AAA_H
//
//#include "bbb.h"
//
//class AAA
//{
//      BBB *b;
//};
//
#endif

class BBB
{
        AAA *a;
};

#endif

class AAA
{
        BBB *b;
};

#endif

int main()
{
   return 0;
}

Now that the preprocessor has completed, the compiler works its way down the code. The first thing it sees (that isn't a # command) is class BBB, which contains an AAA. But AAA hasn't been declared yet, so the compiler gives an error and stops.

Generally, the headers shouldn't have dependencies on things that include them. So if rgl.hpp includes application.hpp, then it's risky for anything in application.hpp to rely on rgl.hpp. Same for audio.hpp, it shouldn't rely on application.hpp, and so on.

One fix for some of these things is to predeclare the types. So aaa.h doesn't really need to include bbb.h if instead it added the line:
class BBB;
That's enough to warn the compiler "hey, don't panic at this BBB thing, I'll fill in the details later". But that only works for pointers or references. You can't have a BBB object without all the details already specified. So this is fine:
class BBB;
BBB *b;
but this is invalid:
class BBB;
BBB b;
because it can't make a BBB without knowing the class contents, but pointers don't care.

Mostly it comes down to reorganising the headers to make sure dependencies don't loop back on themselves. It's rather tricky at times, this is one side of C++ I don't like compared to other languages.

Hmm, that got a bit long. My holidays ended today, so my brain has kicked back into teaching mode. :)

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: General C++ Header Question [SOLVED] + Demo/Quiz
« Reply #4 on: February 01, 2022, 01:06:30 pm »
My problem is SOLVED, thank you for your help :) I suppose hindsight is 20/20, I was doing all kinds of funky things to try and get this to work - I didn't think that was the issue, haha!

Before I went on writing any kind of reply, I had to set up a mock "project" on onlinegdb.com. It's an extremely simplified and portable example of the problem I was having. I didn't want to throw this away, I figured it would be a useful demo of #include gone wrong. I'll save the raw code below for future explorers and code archeologists. By default, this works until you uncomment the lines pointed out within the code.

Problem (Demo)
https://onlinegdb.com/XZITnMQq6
Problem (Raw Code)
(click to show/hide)

Bonus points to anybody who figures out how to return the output below without having the problem of a circular #include issue. This isn't really something I'm going to force (I don't even think I need it nor do I think it would be good format for what I'm doing) but it does pique my curiosity. 8)

Code: [Select]
AAA says BA
BBB says AB

Other than that, my original problem is solved! Thanks for your help kojack! :D

Something tells me you're a teacher/professor? Feel free to use material from any of my questions in a classroom setting. I'm trying to learn/refresh on C++ so hopefully it helps more people than just me.

 

anything