Search code examples
c++staticinitializationuniqueidentifier

c++ incremental type ID across compilation units


I need each derived class to have a unique ID, but the ID's need to be incremental. Here is my current implementation:

//component.cpp
class ComponentId
{
public:
    ComponentId();


    static int nextId;
    int id;
};

template<class T>
class Component {
public:
    static const ComponentId componentId;
};

template<class T>
const ComponentId Component<T, chunkSize>::componentId;

///component.cpp
int ComponentId::nextId = 0;

ComponentId::ComponentId() : id(nextId++)
{
}

//IN static library a
struct compA : public Component<compA> {

}

//In static library b
struct compB : public Component<compB> {

}

//in main.cpp
#include <component.h>
#include <compA.h>
#include <compB.h>
std::cout << compA::componentId.id << std::endl;
std::cout << compB::componentId.id << std::endl;

This works fine in all my unit tests but doesn't seem to work as intended when using multiple compilation units or across static libraries. The ID's are reused in different libraries. One library may have id's 0,1,2 and 3, but another library will also have classes with IDs 0 and 1. My guess is that the nextid field isn't being shared.

I have also tried to use the extern keyword but It seemed to produce the same issue. I have also tried to make a static getId function in hopes to do initialization on first use, but no such luck.

I really need the IDs to be 'tight', as in 1,2,3,4,5, rather than 67, 80, 123, 1, 4.

Any ideas?


Solution

  • Assuming that you want a single int instance shared across multiple TUs, I think that using an inline function that returns a reference to a static int should do the trick:

    // counter.hpp
    namespace detail 
    {
        inline int& getCounter()
        {
            static int result{0};
            return result;
        }
    }
    
    inline int nextId()
    {
        return detail::getCounter()++;
    }
    

    // component.cpp
    ComponentId::ComponentId() : id(nextId())
    {
    }
    

    Nevertheless, this feels brittle and prone to errors, especially if you're aiming to rely on your ID for serialization/deserialization. If you know all the types of your components at compile-time, I suggest using a typelist. E.g.

    using components = typelist<
        Component0,
        Component1,
        Component2,
        Component3
    >;
    

    Otherwise, if you only know your components types at run-time, provide some sort of registry that allows users to register/unregister types in a controlled manner.