c++macrosfactoryfactory-method

How to have Factory registry macros in the beginning of header files?


Currently, the Factory registry macro im using is at the bottom of the header file:

#pragma once

#include "GameObject.h"

class Foo : public GameObject
{
//...
};

ENGINE_SPAWNABLE(Foo);

This macro is functional, but is very hard to find in bigger headers and gets lost in headers with multiple classes. The macro would be best declared at the top of the class like so:

#pragma once

#include "GameObject.h"

ENGINE_SPAWNABLE();
class Foo : public GameObject
{
//...
};

But how would I go about doing this? Currently the macro only knows of the class because it's defined beforehand.

The current registry implementation is as so, only the macro part is important:

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <unordered_map>

#include "Object.h"

// Registers a GameObject class with the factory's registry, and connecting that to a templated SpawnGameObject function. No need to use this if the class is never spawned through the engine
#define ENGINE_SPAWNABLE(CLASSNAME) static bool CLASSNAME##Registered \
= (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#CLASSNAME] = &GameObjectFactory::SpawnObject<CLASSNAME>, true)

// Singleton game object factory
class GameObjectFactory
{
public:
    // Gets instance of the simpleton
    static GameObjectFactory& GetInstance()
    {
        static GameObjectFactory Instance;
        return Instance;
    }

    // A templated function to spawn any registered GameObject
    template <typename TObject>
    static std::unique_ptr<Object> SpawnObject()
    {
        return std::make_unique<TObject>();
    }
    // A factory function that spawns an object of the specified class name
    std::shared_ptr<Object> SpawnGameObjectByName(const std::string& Name);

    std::unordered_map<std::string, std::function<std::shared_ptr<Object>()>>& GetGameObjectRegistry(); // Returns the Registry

private:
    std::unordered_map<std::string, std::function<std::unique_ptr<Object>()>> Registry; // Registry that maps class names to factory functions
};

Any help or nudges towards any relevant resources would be appreciated.


Solution

  • First off, your SpawnObject() method returns std::unique_ptr<Object>, and your Registry map stores functions that return std::unique_ptr<Object>, however your GetGameObjectRegistry() method returns a map of functions that return std::shared_ptr<Object>, and your SpawnGameObjectByName() method returns std::shared_ptr<Object>. So, you should fix this discrepancy. Either you want your objects to use unique ownership or shared ownership, don't mix them.

    That being said, if you want to move the macro above each class being registered, you still have to pass the class type into the macro as a parameter. But, you can have the macro forward-declare the class, eg:

    #define ENGINE_SPAWNABLE(CLASSNAME) \
    class CLASSNAME; \
    static bool CLASSNAME##Registered = (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#CLASSNAME] = &GameObjectFactory::SpawnObject<CLASSNAME>, true)
    
    ENGINE_SPAWNABLE(Foo);
    ...
    class Foo : public GameObject
    {
    //...
    };
    

    Online Demo

    That being said, note that your macro is declaring its bool in global scope as a static variable. As such, every translation unit that you #include Foo.h into will get its own local copy of the bool. If you want to avoid that, then you should declare the bool as an extern variable instead, and then move its definition (and thus the registration) into Foo.cpp instead. Or else, you can make the bool be a static member of the Foo class rather than a global variable.