std::type_index raggiunge l'ordinamento inter-unità di traduzione incapsulando un puntatore a un oggetto std::type_info e delegando il confronto alla funzione membro before() sottostante. L'ABI C++ prescrive che il linker unisca le informazioni sui tipi per tipi identici tra unità di traduzione in un unico oggetto canonico (utilizzando sezioni COMDAT o simboli deboli), o garantisca che before() fornisca un ordinamento totale coerente indipendentemente dalle differenze di indirizzi fisici. Di conseguenza, std::type_index semplicemente avvolge questo confronto garantito dall'ABI, fornendo il supporto per operator< e hash senza richiedere che i tipi siano completi al momento del confronto. Questo meccanismo si basa interamente sull'informazione di tipo a runtime (RTTI) essendo abilitata, poiché il compilatore deve emettere i metadati di tipo necessari affinché il linker possa deduplicare o riconciliare le identità di tipo oltre i confini delle librerie condivise.
Durante la progettazione di un'architettura di plugin per un motore di gioco, avevamo bisogno di un registro centrale che mappasse i tipi di componente a funzioni fabbrica. Ogni plugin (libreria condivisa) registrava i suoi componenti utilizzando typeid(Component).name() come chiave. Tuttavia, durante i test cross-platform, abbiamo scoperto che le ricerche in std::map fallivano in modo intermittente quando un plugin caricato in una libreria condivisa tentava di recuperare una fabbrica registrata dal motore centrale residente in un'altra. La causa principale era che i nomi delle stringhe restituiti da type_info::name() variavano tra i compilatori (GCC vs Clang), e il confronto diretto dei puntatori degli oggetti type_info falliva perché ciascuna libreria condivisa conteneva istanze statiche distinte per lo stesso tipo.
Soluzione 1: Normalizzazione manuale delle stringhe
Abbiamo considerato di demanglare e normalizzare le stringhe type_info::name() utilizzando API specifiche per il compilatore come abi::__cxa_demangle per creare una chiave canonica. Questo approccio prometteva identificatori leggibili per l'uomo adatti per il debugging.
Pro: Le chiavi leggibili facilitano il logging e la serializzazione.
Contro: Il demangling è costoso, il confronto delle stringhe è più lento rispetto al confronto degli interi, e il formato rimane definito dall'implementazione, rischiando che futuri aggiornamenti del compilatore rompano il registro.
Soluzione 2: Ereditarietà virtuale e RTTI personalizzato
Abbiamo esplorato l'idea di richiedere a tutti i componenti di ereditare da una classe base che fornisse un metodo virtuale GetTypeID() che restituisse una costante intera assegnata manualmente.
Pro: Confronti interi deterministici e veloci senza dipendenza dall'RTTI del compilatore.
Contro: L'assegnazione manuale degli ID è soggetta a errori (collisioni), richiede la modifica delle gerarchie di classe e non può gestire tipi di terze parti non sotto il nostro controllo.
Soluzione 3: Adozione di std::type_index
Abbiamo rifattorizzato il registro per utilizzare std::map<std::type_index, FactoryFunc>, utilizzando std::type_index(typeid(T)) come chiave.
Pro: Lo standard garantisce un ordinamento e un hashing coerenti tra unità di traduzione tramite il confronto type_info conforme all'ABI, non richiede gestione manuale degli ID e si integra senza problemi con il codice esistente che utilizza typeid.
Contro: Richiede che l'RTTI sia abilitata (aumentando la dimensione del binario), e gli oggetti type_index non possono essere serializzati per la trasmissione in rete o la memorizzazione persistente.
Abbiamo selezionato Soluzione 3 poiché l'affidabilità dell'identificazione dei tipi tra librerie superava il costo in termini di dimensione binaria dell'RTTI. Il comportamento imposto dallo standard di std::type_index ha eliminato il fragile parsing delle stringhe e la manutenzione manuale degli ID che affliggevano le alternative.
Il registro ha funzionato correttamente oltre i confini delle DLL su Linux, Windows e macOS. Le ricerche delle fabbriche sono diventate confronti O(log N) di puntatori interni piuttosto che operazioni su stringhe, riducendo la latenza di istanziazione dei componenti di circa il 40% rispetto all'approccio di demangling. Il sistema ora supporta il caricamento a caldo dei plugin senza dover ri-registrare i tipi del motore centrale.
std::type_info::name() restituisce una stringa di byte null-terminata definita dall'implementazione; lo standard C++ declina esplicitamente di specificare il suo formato, codifica o stabilità. Ad esempio, GCC restituisce tipicamente nomi malformati (es. "St6vectorIiSaIiEE"), mentre MSVC restituisce nomi leggibili (es. "class std::vector<int,class std::allocator<int> >"). I fornitori di compilatori possono cambiare queste rappresentazioni in versioni future per migliorare il debugging o ridurre le lunghezze dei simboli. Di conseguenza, serializzare queste stringhe su disco o nei protocolli di rete crea comportamento indefinito all'aggiornamento del compilatore, poiché le chiavi precedentemente salvate non corrisponderanno più a quelle generate di nuovo. I candidati spesso presumono erroneamente che name() si comporti come un UUID stabile.
Quando l'RTTI è disabilitata, il compilatore non emette oggetti type_info per tipi polimorfici, e l'operatore typeid diventa malformato (eccetto per espressioni di tipo statico, che restituiscono informazioni di tipo statico in alcune implementazioni, ma generalmente è disabilitato). std::type_index richiede un const std::type_info& per la costruzione, e senza RTTI, i metadati di tipo necessari non esistono nel binario. Poiché questa è una dipendenza a tempo di compilazione dai metadati generati, il compilatore emette un errore (es. "riferimento indefinito a typeinfo for X") durante il linking piuttosto che deferire a un'eccezione a runtime catturabile. I candidati si aspettano spesso un std::bad_typeid a runtime o simile, confondendolo con i fallimenti di dynamic_cast.
std::type_index memorizza internamente un puntatore (o riferimento) a un oggetto std::type_info. I parametri template non di tipo in C++20 e versioni precedenti richiedono tipi strutturali dove tutti i membri sono pubblici e di tipi strutturali (o array di questi), e non possono contenere puntatori a oggetti con indirizzi di memorizzazione dinamica o dipendenti dal linking. Poiché gli oggetti type_info risiedono nella memoria statica con indirizzi dipendenti dal linker, e std::type_index non è un tipo strutturale (ha membri privati e un costruttore di copia non banale in alcune implementazioni), non può essere utilizzato come NTTP. Anche se C++23 consente typeid nelle espressioni costanti, std::type_index stesso rimane non letterale o non strutturale nella maggior parte delle implementazioni della libreria standard, impedendo il suo uso come argomento template dove sono richiesti costanti a tempo di compilazione.