Альтернативные генераторы идентификаторов для типов

В моем проекте у меня есть генератор идентификаторов для типов, которые выглядят примерно так:

class Family {
    static std::size_t identifier;

    template<typename...>
    static std::size_t family() {
        static const std::size_t value = identifier++;
        return value;
    }

public:
    template<typename... Type>
    inline static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

std::size_t Family::identifier{};

Использование:

const auto id = Family::type<FooBar>();

Он отлично работает для моих целей, но имеет некоторые ограничения. Наиболее раздражающим (цель вопроса) является то, что он завершается неудачно при использовании исполняемого файла, который ссылается на общие библиотеки, если все они пытаются создать идентификаторы. Результатом обычно является то, что n-й идентификатор присваивается различным типам через границы, потому что каждая общая библиотека поддерживает свои собственные отдельные Family::identifier,

Некоторые ребята из разделяемой библиотеки отметили, что было бы желательно более надежное решение, но не смогли предложить решение, которое не ухудшило бы производительность (почти все они представляют контейнеры, находят функции и выделяют память).

Есть ли альтернативный подход, который обходил бы вышеупомянутые ограничения, не теряя при этом производительности текущего проекта?

Я искал через SO и нашел несколько интересных ответов. Многим из которых было несколько лет. Вместо этого я хотел бы изучить решения вплоть до последней версии стандарта, если интерфейс существующего класса остается неизменным.
Этот самый интересный. Он использует адреса статических членов для достижения того же, но не соответствует идее последовательно генерируемых идентификаторов.

Примечание: использование RTTI, к сожалению, не вариант.

Примечание: идентификаторы должны генерироваться последовательно и начинаться с 0, как в решении, представленном выше.

3 ответа

Ваша проблема возникает, потому что у вас есть эта строка в заголовочном файле:

std::size_t Family::identifier{};

Поэтому он заканчивается в каждой единице перевода. Вместо этого вам нужно переместить хранилище для этого в исходный файл.cpp, который скомпилирован только один раз, возможно, в собственную общую библиотеку. Тогда будет только один случай identifier в программе, и она будет работать так, как вы собираетесь.

Вы также можете двигаться identifier от того, чтобы быть классом static переменная к глобальной extern один в заголовочном файле (и, как указано выше, определите его в одном файле.cpp).

Если у вас C++17 или более поздняя версия, вы также можете попробовать:

inline std::size_t Family::identifier{};

Хотя язык не гарантирует (и даже не упоминает), что происходит, когда вы используете эту новую функцию через границы общих библиотек, он работает на моем компьютере.

Если вам не требуется, чтобы идентификаторы были последовательными целыми числами, вы можете использовать адрес статического члена шаблона в качестве идентификатора. Преимущество этого подхода в том, что он не требует инициализации во время выполнения (использует статическую инициализацию):

// in a header
class Family {
    template<class...> struct Id { static char const id; };

    template<typename... T>
    static std::size_t family() {
        return reinterpret_cast<std::size_t>(&Id<T...>::id);
    }

public:
    template<typename... Type>
    static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
int main() {
    auto int_id = Family::type<int>();
    auto int_int_id = Family::type<int, int>();
}

Вы также можете сделать это id константа времени компиляции и использовать ее в качестве аргумента шаблона:

// in a header
struct Family {
    template<class...> struct Id { static char const id; };
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
template<char const*>
struct X {};

int main() {
    X<&Family::Id<int>::id> x;
}

Если вас не интересуют последовательные идентификаторы, используйте адрес функции в качестве идентификатора.

template<typename... T>
uintptr_t getID() {
    return reinterpret_cast<uintptr_t>(&getID<T...>);
}

а потом

auto intID = getID<int>();
auto floatID = getID<float>();
...
Другие вопросы по тегам