Skip to content

Instantly share code, notes, and snippets.

@haxfn
Created February 4, 2024 10:59
Show Gist options
  • Select an option

  • Save haxfn/2180450691cd381ed6bdf2749da9171f to your computer and use it in GitHub Desktop.

Select an option

Save haxfn/2180450691cd381ed6bdf2749da9171f to your computer and use it in GitHub Desktop.
A chat on Object Oriented Design

vishal: image Somebody help I think the error says I have something from the GameObject class that has not been over-riden in the child classes But am not sure please help Maybe I have to do public inheritance

Marioalexsan: Did you provide definitions for your GameObject virtual functions in a source file? Either that, or declare them as pure with virtual sf::Vector2f getPosition() const = 0; if you don't need it in the base class.

vishal: Yes I think so: image Ok I'll try that

Marioalexsan: Defining stuff for Paddle doesn't matter

vishal: You mean GameObjects.cpp? no. I only have GameObjects.hpp I wanted to make it abstract

Dbug: If it's a "pure" virtual base class, you need the = 0

vishal: ok, @mario

Dbug: Also on a pure style point of view, I generally recommend to keep the same order of stuff between the various derived levels, here you have "update" followed by "get/set" in the base class, and the opposite in the derived class. It makes it easier to navigate in the code later :)

vishal: @mario Thank you! Working I thought virtual meant that it was intended to be over written I don't know C++ that well

Dbug: optionally

vishal: Oh.

Dbug: it's not uncommon for a derived virtual implementation to call the parent implementation to do things like "specialization" for a specific class but still call some generic behavior common to all the classes

vishal: Ok, I'll fix that

Dbug: When you put the =0 you basically tell the compiler that there is no default implementation, it

vishal: @degub OH

Dbug: that it's mandatory for every derived class to implement it

Marioalexsan: And that makes the class abstract, which means you can't instantiate a GameObject directly. You'll need to create Paddles and Balls and whatnot.

vishal: @dbug Can this happen if the parent function is not virtual?

Marioalexsan: You can call the base class methods if you want, but it's up to you in both cases.

vishal: so what's the use of virtual? If normal functions in parent can be overwritten?

Dbug: If a method is not virtual, it has to be implemented, either in the CPP or inline in the class declaration

vishal: @dbug oh ok makes sense

Marioalexsan: The difference between normal methods and virtual methods is that virtual methods are called based on the object's type at runtime, while normal methods are called with whatever the type of the pointer is.

vishal: Hmm..

Dolphy: @vishal Also you can "override" methods that are not marked as virtual. (I put quotation marks because you do that without using the override keyword). Virtual methods are stored in something called vtable and they allow you to use runtime polymorphism.

vishal: ok @dolpy

Marioalexsan: The typical case of virtual is to allow you to manage "GameObject" pointers and call something like "draw" or "do something" which will do different things depending of the actual type of object. The caller does not care, the calle is the one knowin it was a "ball" or a "paddle"

vishal: Any resource were I can learn this

Dbug: It's generic OOP concepts, all the core stuff is common to C++, Java, etc... implementation details may be different, syntax as well, but the concepts are generic

vishal: All makes sense now: Normal functions have to have function definitions Virtual functions the definition is optional In both cases over-riding can happen Abstract classes have virtual function = 0 ok @dbug thanks

Dbug: One thing to remember is that using a large inheritance graph for game elements is generally not working, using it for base functionalities like displaying, position, updates, etc... is fine

vishal: "ments is generally not working," you mean slow?

Dbug: Many early C++ games made the mistake of having an object graph with "friends" vs "ennemies" with "flying enemies" and "ennemies with guns", etc...

Marioalexsan: Dbug, try not to overcomplicate stuff right now.

Dbug: And then you want to have a "flying ennemy with gun" or a "friend that can fly" and things stop making sense Not overcomplicating, just saying: Don't even try to do that

vishal: ok thanks everyone

Dbug: One level inheritance, like what you have "interface inheritance" is fine and easy to manage

Marioalexsan: @vishal

#include <iostream>

struct BaseVirtual {
    virtual void say() { std::cout << "BaseVirtual" << std::endl; }
};

struct DerivedVirtual : public BaseVirtual {
    virtual void say() { std::cout << "DerivedVirtual" << std::endl; }
};

struct Base {
    void say() { std::cout << "Base" << std::endl; }
};

struct Derived : public Base {
    void say() { std::cout << "Derived" << std::endl; }
};

int main()
{
    BaseVirtual baseVirtual;
    DerivedVirtual derivedVirtual;
    Base base;
    Derived derived;
    
    BaseVirtual* vPtr;
    
    vPtr = &baseVirtual;
    vPtr->say();
    
    vPtr = &derivedVirtual;
    vPtr->say();
    
    Base* ptr;
    
    ptr = &base;
    ptr->say();
    
    ptr = &derived;
    ptr->say();
}

This outputs

BaseVirtual
DerivedVirtual
Base
Base

Dbug: If you find yourself wanting to add one more level of derived, it's probably going to bite you later, so stop and think it :)

vishal: @mario hmm reading

Marioalexsan: For virtual methods, C++ can call the actual method of the runtime object. For non-virtual methods, it can only call the method it can see through the pointer's type. So assigning a Derived to a Base and calling say() will not call Derived's say method.

vishal: Ok so virtual classes are to do things dynamically at run time

Marioalexsan: Pretty much. It's similar to Java (where everything is implicitly virtual) and C# (where you have virtual and abstract).

vishal: but, why doesn't ptr = &derived; ptr->say(); give error ptr was type Base

Marioalexsan: Derived is a subclass to Base, so you can do the assignment

vishal: Hm.. @mario still a little confusing why would someone not allow ptr->Say() to say "Derived" ptr's original type was Base*

Marioalexsan: C++ can't tell what method of which subclass to call if the method is not virtual.

vishal: so if this saying "Derived" is not allowed, the ptr = &Derived should give error while compiling

Marioalexsan: A Base* could be a pointer to any derived object, like Base, Derived, Derived2, Derived3, DerivedFromDerived, etc.

vishal: hm..

Marioalexsan: And the way to make C++ be able to call the relevant method from the relevant subclass is to make the method virtual, which means C++ will create a virtual table to store the data it needs at runtime.

vishal: What is the use case of having a parent pointer point to something to child

Marioalexsan: You can have an array which holds pointers to entities, with the base entity class having virtual void update() = 0; that is overridden in derived entities. That array can hold pointers to Player, Goblin, DynamicWater, Dragon, LevelCompleteTrigger, etc. It doesn't matter what the runtime types of the entities will be, you'll be able to go through the array and update the entities using update(). Without virtual you wouldn't be able to do that.

dolphy: That's how some game engines like Unity work

vishal: hmmm... nice, but what is the use of non-virtual ptr pointing to child Nice!

Marioalexsan: In some cases you only work with stuff from the base class and don't really care what the derived class adds. For example you could take a Transformable* as an input to a function and do some generic transformations on it. It won't matter if it's an actual Transformable or some class that derived from it.

vishal: I am thinking Thanks anyway

Dbug: Just to give some other ideas of what you can do with inheritance, most often used in serious/tools code and less often in actual game code, is that you can derive from multiple base classes to add specific functionalities to your classes. In one of the tools I maintained, we had a class hierarchy of objects like what you have with your GameObject->Paddle and GameObject->Ball But on top of that, our objects would also derive from interfaces classes for specific things like "undo/redo" system, "load/save" serialization, etc...

Marioalexsan: I'm not a huge fan of multiple inheritance of stuff that's not interfaces.

Dbug: Totally, you risk the Diamond Effect, that's why we had a main base class, and zero to n pure interfaces classes just adding a few additional methods

Marioalexsan: That's pretty much the same as C# / Java

Dbug: The main reason why we rarely use that in games but mostly in tools, it's because it tends to be costly because you often need to dynamic_cast all over to check if something implements a specific interface.

Marioalexsan: Ehh The costs of virtual stuff probably pales in comparison to something like one DB call anyway.

Purple Prince Bambo: Not at the scale you'd do it in games where performance is critical Also db calls are often done async anyway.

Dbug: Sure, but this type of thinking is also why the Unreal Engine requires so much memory and CPU to do things engines 10+ years older would have no trouble doing at a lower cost :p

Purple Prince Bambo: And it's an irrelevant factor if the game doesn't call to the db The overhead of the dynamic cast is one it is likely to incur and want to avoid

Dbug: "death by a thousand cuts" type of thinking

Purple Prince Bambo: Amen to that dbug

Marioalexsan: 🤷 That is true too

Dbug: Well, time to work a bit on my game, see ya

Marioalexsan: Game? Better add procedural generation, survival mechanics and an overdetailed RPG skill tree

EOF

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment