webentwicklung-frage-antwort-db.com.de

std :: any ohne RTTI, wie geht das?

Wenn ich std::any Verwenden möchte, kann ich es mit ausgeschaltetem RTTI verwenden. Das folgende Beispiel kompiliert und läuft erwartungsgemäß auch mit -fno-rtti Mit gcc.

int main()
{   
    std::any x;
    x=9.9;
    std::cout << std::any_cast<double>(x) << std::endl;
}

Aber wie speichert std::any Die Typinformationen? Wie ich sehe, habe ich, wenn ich std::any_cast Mit dem "falschen" Typ aufrufe, wie erwartet die Ausnahme std::bad_any_cast.

Wie wird das realisiert oder ist das vielleicht nur ein GCC-Feature?

Ich fand, dass boost::any Auch kein RTTI brauchte, aber ich fand auch nicht, wie das gelöst wird. Braucht boost :: any RTTI? .

Das Stöbern im STL-Header selbst gibt mir keine Antwort. Dieser Code ist für mich fast unlesbar.

23
Klaus

TL; DR; std::any Enthält einen Zeiger auf eine statische Elementfunktion einer Klasse mit Vorlagen. Diese Funktion kann viele Operationen ausführen und ist für einen bestimmten Typ spezifisch, da die tatsächliche Instanz der Funktion von den Vorlagenargumenten der Klasse abhängt.


Die Implementierung von std::any In libstdc ++ ist nicht so komplex, Sie können es sich ansehen:

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any

Grundsätzlich enthält std::any Zwei Dinge:

  • Ein Zeiger auf einen (dynamisch) zugewiesenen Speicher;
  • Ein Zeiger auf eine "Speichermanagerfunktion":
void (*_M_manager)(_Op, const any*, _Arg*);

Wenn Sie ein neues std::any Mit einem Objekt vom Typ T konstruieren oder zuweisen, zeigt _M_manager Auf eine Funktion, die für den Typ T spezifisch ist (bei der es sich tatsächlich um eine handelt) statische Elementfunktion der Klasse, die für T) spezifisch ist:

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) { /* ... */ }

Da diese Funktion für einen bestimmten Typ spezifisch ist, benötigen Sie RTTI nicht, um die für std::any Erforderlichen Operationen auszuführen.

Außerdem ist es einfach zu überprüfen, ob Sie in std::any_cast Den richtigen Typ einstellen. Hier ist der Kern der gcc-Implementierung von 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;
}

Sie sehen, dass es sich lediglich um eine Gleichheitsprüfung zwischen der gespeicherten Funktion in dem Objekt handelt, das Sie umwandeln möchten (_any->_M_manager) Und der Managerfunktion des Typs, in den Sie umwandeln möchten (&any::_Manager<decay_t<_Tp>>::_S_manage). .


Die Klasse _Manager<_Tp> Ist tatsächlich ein Alias ​​für _Manager_internal<_Tp> Oder _Manager_external<_Tp>, Abhängig von _Tp. Diese Klasse wird auch für die Zuweisung/Erstellung von Objekten für die Klasse std::any Verwendet.

30
Holt

Eine der möglichen Lösungen besteht darin, eine eindeutige ID für jeden Typ zu generieren, der möglicherweise in any gespeichert ist (ich nehme an, Sie wissen mehr als, wie any intern funktioniert). Der Code, der das kann, könnte ungefähr so ​​aussehen:

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;
        }
    };    
};

Wenn dies implementiert ist, können Sie die ID des Typs anstelle von RTTI typeinfo verwenden, um den Typ schnell zu überprüfen.

Beachten Sie die Verwendung statischer Variablen in Funktionen und statischen Funktionen. Dies geschieht, um das Problem der undefinierten Reihenfolge der Initialisierung statischer Variablen zu vermeiden.

2
bartop

Die manuelle Implementierung einer eingeschränkten RTTI ist nicht so schwierig. Sie benötigen statische generische Funktionen. Soviel kann ich sagen, ohne eine vollständige Implementierung bereitzustellen. Hier ist eine Möglichkeit:

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;};
};

Dies ist meine erste Beobachtung; alles andere als ideal, es fehlen viele details. Man kann eine Instanz von meta als nicht statisches Datenelement seiner angenommenen Implementierung von std::any Verwenden.

1
Red.Wave