web-dev-qa-db-ja.com

std :: any RTTIなしで、それはどのように機能しますか?

std::anyを使用する場合は、RTTIをオフにして使用できます。次の例は、gccを使用した-fno-rttiでも期待どおりにコンパイルおよび実行されます。

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

しかし、どのようにstd::anyが型情報を保存するのでしょうか?ご覧のとおり、「間違った」タイプでstd::any_castを呼び出すと、予想どおりstd::bad_any_cast例外が発生しました。

それはどのように実現されますか、これはおそらくgcc機能のみですか?

boost::anyもRTTIを必要としないことがわかりましたが、それを解決する方法も見つかりませんでした。 boost :: anyはRTTIを必要としますか?

STLヘッダー自体を掘り下げても、答えは得られません。そのコードは私にはほとんど読めません。

23
Klaus

TL; DR;std::anyは、テンプレートクラスの静的メンバー関数へのポインタを保持します。この関数は多くの操作を実行でき、関数の実際のインスタンスはクラスのテンプレート引数に依存するため、特定の型に固有です。


Libstdc ++でのstd::anyの実装はそれほど複雑ではないので、ご覧ください。

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

基本的に、std::anyは2つのことを保持します。

  • (動的に)割り当てられたストレージへのポインター。
  • 「ストレージマネージャー機能」へのポインター:
void (*_M_manager)(_Op, const any*, _Arg*);

タイプTのオブジェクトで新しいstd::anyを作成または割り当てると、_M_managerはタイプTに固有の関数を指します(実際には静的メンバーです) 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) { /* ... */ }

この関数は特定の型に固有であるため、std::anyで必要な操作を実行するためにRTTIは必要ありません。

さらに、std::any_cast内で正しい型にキャストしていることを簡単に確認できます。 std::any_castのgcc実装のコアは次のとおりです。

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

これは、キャストしようとしているオブジェクト内のストアド関数(_any->_M_manager)とキャストしたいタイプのマネージャー関数(&any::_Manager<decay_t<_Tp>>::_S_manage)間の単純なチェックであることがわかります。


クラス_Manager<_Tp>は、実際には_Manager_internal<_Tp>に応じて_Manager_external<_Tp>または_Tpのエイリアスです。このクラスは、std::anyクラスのオブジェクトの割り当て/構築にも使用されます。

30
Holt

考えられる解決策の1つは、anyに格納されている可能性のあるすべてのタイプに対して一意のIDを生成することです(anyが内部的にどのように機能するかは知らないと思います)。それを行うことができるコードは次のようになります。

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

これを実装すると、RTTI typeinfoの代わりにタイプのIDを使用して、タイプをすばやく確認できます。

関数および静的関数内での静的変数の使用に注意してください。これは、静的変数の初期化の未定義の順序の問題を回避するために行われます。

2
bartop

限られたRTTIの手動実装はそれほど難しくありません。静的汎用関数が必要になります。完全な実装を提供しなくても、それだけ言えます。ここに1つの可能性があります。

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

これは私の最初の観察です。理想とはほど遠い、多くの詳細が欠けています。 metaの1つのインスタンスを、std::anyの想定される実装の非静的データメンバーとして使用できます。

1
Red.Wave