Si je veux utiliser std::any
Je peux l'utiliser avec RTTI éteint. L'exemple suivant se compile et s'exécute comme prévu également avec -fno-rtti
avec gcc.
int main()
{
std::any x;
x=9.9;
std::cout << std::any_cast<double>(x) << std::endl;
}
Mais comment std::any
stocke les informations de type? Comme je vois, si j'appelle std::any_cast
avec le "mauvais" type que j'ai obtenu std::bad_any_cast
exception comme prévu.
Comment est-ce réalisé ou est-ce peut-être seulement une fonctionnalité gcc?
Je l'ai trouvé boost::any
n'avait pas non plus besoin de RTTI, mais je n'ai pas trouvé non plus comment cela était résolu. Est-ce que boost :: any a besoin de RTTI? .
Creuser dans l'en-tête STL lui-même ne me donne aucune réponse. Ce code est presque illisible pour moi.
TL; DR; std::any
contient un pointeur sur une fonction membre statique d'une classe basée sur un modèle. Cette fonction peut effectuer de nombreuses opérations et est spécifique à un type donné car l'instance réelle de la fonction dépend des arguments de modèle de la classe.
L'implémentation de std::any
dans libstdc ++ n'est pas si complexe, vous pouvez y jeter un œil:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any
Fondamentalement, std::any
contient deux choses:
void (*_M_manager)(_Op, const any*, _Arg*);
Lorsque vous créez ou attribuez un nouveau std::any
avec un objet de type T
, _M_manager
pointe vers une fonction spécifique au type T
(qui est en fait une fonction membre statique de classe spécifique à T
):
template <typename _ValueType,
typename _Tp = _Decay<_ValueType>,
typename _Mgr = _Manager<_Tp>, // <-- Class specific to T.
__any_constructible_t<_Tp, _ValueType&&> = true,
enable_if_t<!__is_in_place_type<_Tp>::value, bool> = true>
any(_ValueType&& __value)
: _M_manager(&_Mgr::_S_manage) { /* ... */ }
Puisque cette fonction est spécifique à un type donné, vous n'avez pas besoin de RTTI pour effectuer les opérations requises par std::any
.
De plus, il est facile de vérifier que vous effectuez un cast vers le bon type dans std::any_cast
. Voici le cœur de l'implémentation gcc de std::any_cast
:
template<typename _Tp>
void* __any_caster(const any* __any) {
if constexpr (is_copy_constructible_v<decay_t<_Tp>>) {
if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage) {
any::_Arg __arg;
__any->_M_manager(any::_Op_access, __any, &__arg);
return __arg._M_obj;
}
}
return nullptr;
}
Vous pouvez voir qu'il s'agit simplement d'une vérification d'égalité entre la fonction stockée à l'intérieur de l'objet que vous essayez de caster (_any->_M_manager
) et la fonction de gestionnaire du type vers lequel vous souhaitez caster (&any::_Manager<decay_t<_Tp>>::_S_manage
).
La classe _Manager<_Tp>
est en fait un alias pour _Manager_internal<_Tp>
ou _Manager_external<_Tp>
cela dépend de _Tp
. Cette classe est également utilisée pour l'allocation/construction d'objet pour le std::any
classe.
Une des solutions possibles consiste à générer un identifiant unique pour chaque type éventuellement stocké dans any
(je suppose que vous savez sans savoir comment any
fonctionne en interne). Le code qui peut le faire peut ressembler à ceci:
struct id_gen{
static int &i(){
static int i = 0;
return i;
}
template<class T>
struct gen{
static int id() {
static int id = i()++;
return id;
}
};
};
Avec cette implémentation Vous pouvez utiliser l'ID du type au lieu de RTTI typeinfo
pour vérifier rapidement le type.
Notez l'utilisation de variables statiques à l'intérieur des fonctions et des fonctions statiques. Ceci est fait pour éviter le problème de l'ordre indéfini d'initialisation des variables statiques.
La mise en œuvre manuelle d'un RTTI limité n'est pas si difficile. Tu vas avoir besoin de fonctions génériques statiques. Je peux dire cela sans fournir une mise en œuvre complète. voici une possibilité:
class meta{
static auto id(){
static std::atomic<std::size_t> nextid{};
return ++nextid;//globally unique
};
std::size_t mid=0;//per instance type id
public:
template<typename T>
meta(T&&){
static const std::size_t tid{id()};//classwide unique
mid=tid;
};
meta(meta const&)=default;
meta(meta&&)=default;
meta():mid{}{};
template<typename T>
auto is_a(T&& obj){return mid==meta{obj}.mid;};
};
Ceci est ma première observation; loin d'être idéal, manque de nombreux détails. On peut utiliser une instance de meta
comme membre de données non statique de sa supposée implémentation de std::any
.