Lots of syntax errors when using abstract classes

Started by
12 comments, last by Alberth 8 years, 8 months ago

Hey all,

I recently started developing my own game engine (just for fun), but I've run into a really weird error when implementing abstract classes. To help you all understand the issue I'm having, I've attached a simply UML diagram of how my game engine currently works (subject to change).

[spoiler]KjRSpre.png[/spoiler]

This is a pretty simple design. My engine initializes and keeps track of several different modules or components called managers. Each individual manager needs access to some of the same types of information, so I thought it'd be easiest to create an abstract class that they all derive from. The abstract class contains a constructor, an init() function (this is the abstract function that all managers should override), a reference to the manager, and a reference to the engine.

I'm using Visual Studio 2013, and in the viewport I see no errors of any kind. I'm pretty sure I've implemented this correctly, but when I try to compile, I get a slew of different kinds of errors (mostly syntax) that reference "blank" spots in the code.

Here's the full list of errors:

[spoiler]JbNjK7K.png[/spoiler]

If you reference these areas of the code, they all point to the beginning of a line, and their description makes little sense in the context. The 3 main headers that are receiving these errors are BuddyEngine.h, Manager.h, and RenderManager.h. I'll post the code for you to see. These are all pretty simple implementations.

BuddyEngine.h

[spoiler]


#pragma once
#ifndef BUDDYENGINE_H
#define BUDDYENGINE_H

#include "RenderManager.h"
//#include "InputComponent.h"
//#include "SoundComponent.h"

class BuddyEngine {
public:
	BuddyEngine(int argc, char **argv);

	RenderManager& getRenderManager();

	static BuddyEngine& getInstance();

private:
	RenderManager* renderManager;

	static BuddyEngine* instance;
};

#endif

[/spoiler]

Manager.h

[spoiler]


#pragma once
#ifndef MANAGER_H
#define MANAGER_H

#include "BuddyEngine.h"

class Manager {
public:
	Manager(BuddyEngine* engine, int argc, char **argv);

	BuddyEngine& getBuddyEngine();

	static Manager& getInstance();

private:
	BuddyEngine* buddyEngine;

	static Manager* instance;

	virtual bool init(int argc, char **argv) = 0;
};

#endif

[/spoiler]

RenderManager.h

[spoiler]


#pragma once
#ifndef RENDERMANAGER_H
#define RENDERMANAGER_H

#include "Manager.h"
#include "WorldRender.h"

class RenderManager : public Manager {
public:
	RenderManager(BuddyEngine* engine, int argc, char **argv) : Manager(engine, argc, argv) {}

	GLFWwindow& getWindow();

	WorldRender& getWorldRender();

private:
	GLFWwindow* window;

	WorldRender* worldRender;

	virtual bool init(int argc, char **argv);

	static void render();
};

#endif

[/spoiler]

I hate having to post lots of code for help, but I'm afraid I've hit a brick wall with this ghost of an issue. If anyone has any ideas of what could be causing these errors, please let me know. If you need me to post the implementations of these functions (.cpp files), I can do that as well. Thanks for reading!

Advertisement
You've got circular dependencies. Your header files are referencing each other. You can probably break some of this by forward declaring BuddyEngine in Manager.hpp instead of including the BuddyEngine header.

[edit]
I'm on my phone right now, so not easy to go into more detail.

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

You've got circular dependencies. Your header files are referencing each other. You can probably break some of this by forward declaring BuddyEngine in Manager.hpp instead of including the BuddyEngine header.

[edit]
I'm on my phone right now, so not easy to go into more detail.

I literally just discovered this and came back here to update it. I'm new to C++ and this is something I'm having to get used to. Dependencies don't seem to flow as nicely, and as "OOP-y", as imports do in Java, which is what I'm used to.

Thanks for the verification!

You can have class declarations without content if you never use its internals.

class B;

class A {
public:
    A(B *b);
    ~A();

    B *b;
};
I don't know exactly how that works with references (you'h have to look that up), but this is a useful technique to avoid recursive includes.

Another option is to move most of the #include into the .cpp files, but that makes .h file dependent on #include-ing other .h files before it without clear specification what you need where.


I am also somewhat wondering why you return a reference like GLFWwindow& getWindow(); while internally you have a pointer GLFWwindow* window; Unlike Java, C++ does not have nullptr references.
From that perspective, making window a contained data member may make more sense? Another option is of course to return a pointer at the public interface as well.
(your solution is an option too, if you get the pointer after construction, but you do always get it, so you never have to return a nullptr.

Last but not least, please be aware that you cannot call derived methods until you have arrived in the constructor of the derived class. That means here that you cannot call 'init' from the constructor of the base class.
Since it's a private method, I am not sure who is going to call it from the base class, in this code.

You can have class declarations without content if you never use its internals.
[...]
I don't know exactly how that works with references (you'h have to look that up), but this is a useful technique to avoid recursive includes.


Never using its internals is one part of the coin. The other side is that you cannot declare a variable (local, global or member) of that type while it is incomplete (that is, if the full body is not known at that point) nor instantiate the type via any flavor of dynamic allocation. Pointers and references to the type are no problem since the compiler does not need to know anything about the type except that it exists.
Using an incomplete type as a template parameter will depend on the template. It might refuse to compile at all (like std::unique_ptr which needs more knowledge about the type) or work fine (like std::shared_ptr or std::vector), possibly with some limitation regarding what member functions you can call while the type is incomplete.

Using an incomplete type as a template parameter will depend on the template. It might refuse to compile (like std::unique_ptr which needs more knowledge about the type) or work fine (like std::shared_ptr or std::vector), possibly with some limitation regarding what member functions you can call while the type is incomplete.

It's true that there are some templates that require a fully-defined type, but std::unique_ptr<> is certainly not one of them. The only place you need to have a fully defined type for that template class is where its destructor is invoked, at that should if at all possible be in an implementation file, not a header file.

A good general rukle of thumb is to forward declare classes if you can, and only include their definition (header) files if you must. That rule speeds up compile times, breaks circular dependencies, and minimizes the surface for unintentionally exposing the wrong interface.

Stephen M. Webb
Professional Free Software Developer

Mhm. I remember having had considerable amounts of stress with std::unique_ptr and incomplete types. According to the documentation the only issue should be with std::default_deleter for some operations, but I remember my problems being more annoying. Maybe because it was early in C++11s life cycle or I had a secondary issue?

So here would be a modified version of your code (if you haven't fixed it already).

BuddyEngine.h

[source]

#pragma once

#ifndef BUDDYENGINE_H

#define BUDDYENGINE_H

class RenderManager;

class BuddyEngine {

public:

BuddyEngine(int argc, char **argv);

RenderManager& getRenderManager();

static BuddyEngine& getInstance();

private:

RenderManager* renderManager;

static BuddyEngine* instance;

};

#endif

[/source]

Manager.h

[source]

#pragma once

#ifndef MANAGER_H

#define MANAGER_H

class BuddyEngine;

class Manager {

public:

Manager(BuddyEngine* engine, int argc, char **argv);

BuddyEngine& getBuddyEngine();

static Manager& getInstance();

private:

BuddyEngine* buddyEngine;

static Manager* instance;

virtual bool init(int argc, char **argv) = 0;

};

#endif

[/source]

RenderManager.h

[source]

#pragma once

#ifndef RENDERMANAGER_H

#define RENDERMANAGER_H

#include "Manager.h"

class WorldRender;

class RenderManager : public Manager {

public:

RenderManager(BuddyEngine* engine, int argc, char **argv) : Manager(engine, argc, argv) {}

GLFWwindow& getWindow();

WorldRender& getWorldRender();

private:

GLFWwindow* window;

WorldRender* worldRender;

virtual bool init(int argc, char **argv);

static void render();

};

#endif

[/source]

Then in your implementation files (.cpp) you can include the appropriate headers.

[edit]

Theoretically fixed the inheritance issues mentioned below

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

That will still not work, because, making a class you want to derive from a singleton.

Which of the 3 derived classes should it return? How can the base class know its derived classes anyway?

Just cut out anything remotely related to a singleton, global or static variable. You have a broken copy of the singleton pattern anyway, cause it allows contructing more than a single one.

While you are at it, don't name anything engine or manager which signals "messy agglomeration of code violating the SRP", choose good names representing the single responsibility of each class.

My guess is, the code inside the cpp files might just be collapsed into about a dozen lines doing calls to GLFW to init, open a window, clean up and about twice as much to implement the needed callbacks for input and there is nothing which actually needs a manager?


That will still not work, because, making a class you want to derive from a singleton.

Your correct. I completely missed the inheritance there.

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]

This topic is closed to new replies.

Advertisement