web-dev-qa-db-fra.com

std :: any sans RTTI, comment ça marche?

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.

23
Klaus

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:

  • Un pointeur vers un stockage alloué (dynamiquement);
  • Un pointeur vers une "fonction de gestionnaire de stockage":
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.

30
Holt

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.

2
bartop

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.

1
Red.Wave