Interdependent header files and class/methods can’t compile

I’m currently working on building a small ECS system, and in the process, I created an Application.h and Gameobject.h, where the Gameobject.h file requires both the declaration of the application class and some of its methods, and Application.h requires the gameobject class and some of its methods. I tried using a forward declaration of both the classes, but that doesn’t help when it comes to getting class methods that are yet undefined, as the forward declaration doesn’t define the class methods.

This is Gameobject.h:

    #pragma once

#include "Common.h"

class Gameobject
{
    friend class Application;

public:
    Gameobject(std::string name)
        :name(name) 
    {
        Application::Get()->AddGameobject(this);
    }

    std::string name;

    template<typename TComponent>
    void AddComponent()
    {
        DERIVES_FROM_COMPONENT;

        TComponent* component = new TComponent();
        component->SetGameobject(this);

        m_Components.push_back((Component*)&component);
    }   
    
    template<typename TComponent>
    void RemoveComponent()
    {
        for (const Component& component : m_Components)
        {
            if (typeid(component) == TComponent)
            {
                m_Components.erase(component);
                delete component; 
                
                return;
            }
        }
    }

private:

    void OnUpdate()
    {
        for (Component* component : m_Components)
        {
            component->OnUpdate();
        }
    }

    std::vector<Component*> m_Components;
};

And Appplication.h:

#pragma once

#include "Common.h"

class Application
{
    friend class Gameobject;

public:

    static Application* Get()
    {
        //make a unique pointer later
        static Application* s_Instance = new Application();

        return s_Instance;
    }

    void Run()
    {
        std::cout << "Running";

        while (isRunning)
        {
            for (Gameobject* gameobject : m_Gameobjects)
            {
                gameobject->OnUpdate();
            }
        }
    }

    bool Close()
    {
        return true;
    }

    bool isRunning = true;

private:

    Application() {};

    ~Application() { delete Get(); }

    void AddGameobject(Gameobject* gameobject)
    {
        m_Gameobjects.push_back(gameobject);
    }

    void RemoveGameobject(Gameobject* gameobject)
    {
        const auto& it = std::find(m_Gameobjects.begin(), m_Gameobjects.end(), gameobject);
        m_Gameobjects.erase(it);
    }

    std::vector<Gameobject*> m_Gameobjects;
};

then Common.h:

#pragma once

#include <iostream>
#include <vector>

class Gameobject;

#include "ECS.h"
#include "Components.h"

#include "Application.h"
#include "Gameobject.h"

So the gameobject constructor requires the application.AddGameobject method and Application.Run() require gameobject.OnUpdate()

I’m still relatively new to C++, so is there something I’m missing that would make linking and compiling this easier or otherwise just solve the issue?

-thanks

Answer

This is a common pitfall from those coming from Java (and likely C#) backgrounds. C++ sparingly defines class methods in header files, and only if they don’t require any other dependencies, other than the class itself — perhaps something else might be #included that they can use, but that’s going off on a tangent. Anyway, in your GameObject.h header file:

Gameobject(std::string name)
    :name(name) 
{
    Application::Get()->AddGameobject(this);
}

In order to compile this in C++, Application class to be fully defined. Unless that class is fully defined, your C++ compiler has no idea what AddGameObject is all about. It’s some mysterious method of whatever Get() returns, which is not defined anywhere, either. It’s a big mystery to your C++ compiler, which is compiling this header file and has no other information about the Application class other than its name.

To do this Application‘s header file can simply be #included, of course. But then you will discover that Application‘s methods also require GameObject to be fully defined as well, before they can get compiled. Somebody has to go first. Somebody has to be fully defined first. But you have two classes whose methods require the other class to be fully defined first.

This is why in C++ class methods are rarely fully defined, inline, in the header file. Here, you only need to declare the constructor in the header file:

class Gameobject
{
public:
    Gameobject(std::string name);

(it’s also unclear that Application if needs to be a friend of this class in the first place). Then in the Application.cpp file, you #include both header files, and define the constructor:

GameObject::GameObject(std::string name)
    :name{name} // Might as well start using modern C++'s uniform initialization syntax 
    {
        Application::Get()->AddGameobject(this);
    }

Since both classes are now fully defined, by including their header files, this constructor can now compile, but only after fixing all other, similar, mutually-dependent methods. You’ll need to remove all of them from the header files, and only declare them; then fully defining them in their appropriate .cpp files, after #includeing all required header files, for both classes.

Leave a Reply

Your email address will not be published. Required fields are marked *