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...
RGL.hpp
#include <istream>
#include <iostream>
#include <string>
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Window.hpp>
#include <RGL/Application.hpp>
RGL/Application.hpp
#include <RGL/Audio.hpp>
#include <RGL/Graphics.hpp>
#include <RGL/Input.hpp>
RGL/Audio.hpp
#include <RGL.hpp>
#include <RGL/Audio.hpp>
// #include <RGL/AudioFile.hpp> (Not implemented yet)
#include <SFML/Audio.hpp>
#include <SFML/System/Time.hpp>
#include <deque>
#include <stdlib.h>
#include <iostream>
#include <istream>
RGL/AudioFile.hpp (Unused)
#include <string>
#include <RGL.hpp>
#include <RGL/Audio.hpp>
#include <SFML/Audio.hpp>
#include <SFML/System/Time.hpp>
RGL/Graphics.hpp
#include <RGL.hpp>
#include <iostream>
#include <string>
#include <SFML/Graphics.hpp>
RGL/Keyboard.hpp (Unused)
#include <RGL.hpp>
#include <SFML/System.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <forward_list>
#include <algorithm>
#include <iostream>
#include <vector>
RGL/Input.hpp
#include <RGL.hpp>
#include <SFML/System.hpp>
// #include <RGL/Keyboard> // <--Not yet implemented
Main.cpp
#include <RGL.hpp>
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.
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. :)
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)
// FILE : aaa.h
#ifndef AAA_H
#define AAA_H
#include "iostream"
//#include "bbb.h" // <--Uncomment this for error ;)
class AAA {
public:
//BBB mB; // <--Uncomment this for error ;)
void sayWhat();
};
#endif
// FILE : bbb.h
#ifndef BBB_H
#define BBB_H
#include "aaa.h"
class BBB {
public:
AAA mA;
void sayWhat();
};
#endif
// FILE : ccc.h
#ifndef CCC_H
#define CCC_H
#include "bbb.h"
class CCC {
public:
AAA mA;
void sayWhat();
};
#endif
// FILE : aaa.cpp
#include "aaa.h"
void AAA::sayWhat() { std::cout << "A"; }
// FILE : bbb.cpp
#include "bbb.h"
void BBB::sayWhat() { mA.sayWhat(); std::cout << "B"; }
// FILE : ccc.cpp
#include "ccc.h"
void CCC::sayWhat() { mA.sayWhat(); std::cout << "C"; }
// FILE : main.cpp
#include <iostream>
using namespace std;
#include "aaa.h"
#include "bbb.h"
#include "ccc.h"
int main() {
AAA mA; cout << "AAA says "; mA.sayWhat(); cout << endl;
BBB mB; cout << "BBB says "; mB.sayWhat(); cout << endl;
CCC mC; cout << "CCC says "; mC.sayWhat(); cout << endl;
cout<<"Hello World";
return 0;
}
Without error, output is...
AAA says A
BBB says AB
CCC says AC
Hello World
...Program finished with exit code 0
Press ENTER to exit console.
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)
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.