Reputation: 83
I'm writing a small Game-Engine (which is used as a library) while re-reading myself into c++. I've coded in Java for several years, but it seems like c++ handles polymorphism a little bit different and I am encountering the following problem:
I have a class GameObject, which represents the basic functionalities of any type of object within the game. So this basically is used as an abstract class. I have several Objects, which override GameObject including a class Map. These objects are finally used within the game.
All this is compiled into a library and used by the game.
Here is the basic code I use: gameobject.h:
class GameObject {
public:
void loadFromJson(nlohmann::json& json);
void load();
// [...] not relevant
}
gameobject.cpp:
void GameObject::loadFromJson(nlohmann::json& json) {
// [...] not relevant
std::cout << "base-class function" << std::endl;
}
void GameObject::load() {
// [...] not relevant
this->loadFromJson(json);
}
map.h:
class Map : public GameObject {
public:
void loadFromJson(nlohmann::json& json);
// [...] not relevant
}
map.cpp:
void Map::loadFromJson(nlohmann::json& json) {
// [...] not relevant
GameObject::loadFromJson(json);
std::cout << "derived class function" << std::endl;
}
main within game:
int main() {
Map* map = new Map(...);
map->load();
}
I'm not sure which arguments within the makefile are relevant or not, therefore I simply paste all I use, including libraries, which should be irrelevant: makefile, gameengine (called gct):
COMPARGS=-Wall -I$(INCLUDEPATH) -I$(LIBPATH) -I$(LIBPATHGLADINCLUDE) -std=c++17 -fsanitize=address -fPIC $(SDL_FLAGS) $(PKG_FLAGS) -lplibfnt -lSOIL -lstdc++fs -g
$(OUTPUTPATH)libgct.so: [...] gameobject [...] map
$(CC) -shared [...] gameobject [...] map -o $(OUTPUTPATH)libgct.so
gameobject: $(SRCPATH)gameobject.cpp $(INCLUDEPATHGCT)gameobject.h
$(CC) $(SRCPATH)gameobject.cpp -c -o gameobject $(COMPARGS)
map: $(SRCPATHMAP)map.cpp $(INCLUDEPATHGCTMAP)map.h
$(CC) $(SRCPATHMAP)map.cpp -c -o map $(COMPARGS)
makefile, game:
COMPARGS=-Wall -std=c++17 -fsanitize=address -I$(LIBINCLUDE) -lgct -lplibfnt -lplibul $(SDL_FLAGS) $(PKG_FLAGS) -lSOIL -lstdc++fs -g
testgame: $(SRCPATH)main.cpp [...]
$(CC) $(SRCPATH)main.cpp [...] $(COMPARGS) -o testgame
Now my expected behavior would be the output: "base-class function" "derived class function"
But what I get is: "base-class function"
I already had problems with using polymorphism within the library in the way, that loadFromJson() was virtual in base class. And using it with an array of GameObjects (at that time it was with other classes and other methods, but the priciple behind it should become clear). This caused strange errors, which google explained with that such polymorphism is slow, and not supposed to be used within libraries. I know, there shouldn't be much of a difference with this way of polymorphism, but I don't get any errors. Just not the expected behavior and I have the feeling this should work.
However that was a different problem with other classes and other methods which I was able to workaround. But this would be quite anoying, if I wasn't able to use it, since I would have to rewrite load() within the derived classes over and over again. I found other posts with a (relativly) similar Problem regarding polymorphism, but there the answers always represented my expected behavior. So my question is: What are the possibilities why this is not working as I want to, or more precise: could this be a result of using the code as a shared library? Maybe there is a error within the code I can't find...I hope and think, that the code I gave you should be enough. I doupt, that it's anything else. If, however, the problem must be somewhere else, I would be glad about some potential mistakes, that could cause this.
Upvotes: 2
Views: 205
Reputation: 118350
void GameObject::load() {
// [...] not relevant
this->loadFromJson(json);
}
This will always invoke GameObject::loadFromJson
, even when you declared a subclass Map
with a loadFromJson
method.
In Java you will expect an instance of Map
to result in Map
's loadFromJson
() getting invoked from load()
. However, C++ is not Java, and ordinary class methods do not get overridden from subclasses by default.
To make them overridable you must use the virtual
keyword in the base class:
class GameObject {
public:
virtual void loadFromJson(nlohmann::json& json);
void load();
// [...] not relevant
};
This will make this method work similarly to how the equivalent code in Java would work, and Map
's method will now get invoked, in this case.
Furthermore, this is not required but you should also explicitly declare the subclass method as virtual
with the override
keyword, when using the current C++ standard:
class Map : public GameObject {
public:
virtual void loadFromJson(nlohmann::json& json) override;
// [...] not relevant
};
This is not required to implement a virtual method dispatch, but this will help your C++ compiler yell at you if you make a mistake or a typo, in the future. It's quite common to add a parameter, or change something about the method in the base class, but forget to do it in the subclass. Marking a subclass method as a virtually overridden method will result in a compilation error, were that to happen, alerting you to the problem.
For more information about virtual methods and how to correctly override them (which in C++ you can control on a method-by-method basis), see the relevant chapters in your C++ book.
Upvotes: 2
Reputation: 36399
The difference is that Java methods are virtual by default c++ methods aren't, you need to explicitly mark any methods you want to be virtual with the virtual
keyword.
class GameObject {
public:
virtual void loadFromJson(nlohmann::json& json);
void load();
// [...] not relevant
}
Its a good idea to mark your derived methods with override
so the compiler raises an error if your method doesn't override a base class method:
class Map : public GameObject {
public:
void loadFromJson(nlohmann::json& json) override;
// [...] not relevant
}
Upvotes: 3