本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

C++のtypeidとtype_infoとtype_index

10月の論文集が発表されるまで暇なので、typeidとtype_infoと、C++11から追加されたtype_indexの解説をする。

C++でRTTI(Run Time Type Infomation/Identification)と呼ばれる機能がある。

std::type_infoは、型情報を表現するクラスである。type_infoのオブジェクトは、typeid式で得ることができる。typeid式は、オペランドに式かtype-idを取る。typeid式の結果は、constなstd::type_info型のlvalueである。typeid式を使うには、typeinfoヘッダーをincludeする必要がある。

#include <typeinfo>

std::type_info const & t1 = typeid(int) ;
std::type_info const & t2 = typeid(double) ;

std::type_infoはデフォルト構築もコピーもムーブもできないクラスである。std::type_info型のオブジェクトは、typeid式によってしか作れない。

typeid式は、constなtype_info型のlvalueを返すので、type_infoを使う場合は、リファレンスかポインターで参照しなければならない。


std::type_info const & t1 = typeid(int) ;
std::type_info const * t2 = &typeid(int) ;

typeid式が返すオブジェクトの寿命は、通常とは変わっていて、プログラムが終了するまでである。つまり、以下のようなコードが動く。

#include <typeinfo>

int main()
{
    std::typeinfo const * ptr ;
    {
        ptr = &typeid(int) ;
    }

    ptr->name() ; // OK、
}

typeidが返すオブジェクトの寿命はプログラムの終了までなので、リファレンスやポインターで安全に保持できる。寿命や解放を考える必要はない。

type_infoのオブジェクトは型を表現する。type_infoのオブジェクト同士を等号比較することで、型が等しいかどうかを調べられる。


typeid(int) == typeid(double) ; // false

また、nameというメンバー関数を持っていて、これは実装依存の型を表現する何らかのnull終端された文字列を返す。

int main()
{
    std::cout << typeid(int).name() << '\n' ;
}

GNU/Linuxでは、IntelのItanium用のC++ ABIがデファクトスタンダードとなっていて、GNU/Linux用のGCCやClangでは、この仕様に従ったマングル名が返される。デマングルして人間にわかりやすい型にするには、以下のようにすればよい。

#include <cxxabi.h>
#include <cstdlib>
#include <string>
#include <memory>

struct free_delete
{
    template < typename T >
    void operator ()( T * ptr ) const noexcept
    {
        std::free( ptr ) ;
    }
} ;

std::string demangle( std::type_info const & ti )
{
    int status = 0 ;
    std::unique_ptr<char, free_delete >
        ptr( abi::__cxa_demangle( ti.name(), nullptr, nullptr, &status ) ) ;

    if ( !ptr )
    {
        switch ( status )
        {
        case -1 :
            return "memory allocation failure" ;
        case -2 :
            return "invalid mangled name" ;
        case -3 :
            return "invalid arguments" ;
        default :
            return "Shouldn't reach here" ;
        }
    }

    std::string result( ptr.get() ) ;

    return result ; 
}

それはさておき・・・

typeid式が実行時型情報と呼ばれている理由は、ポリモーフィッククラス型の式をオペランドに与えると、実行時になるまで分からない型を表現するtype_infoが返されるからだ。

struct A
{
    virtual void polymorphic() { }
} ;

struct B : A
{ } ;

void f( A & ref )
{
    // refがA, Bどちらを参照するのかは実行時に決まる
    // tiが表現する型も実行時に決まる
    decltype(auto) ti = typeid( ref ) ;
}

これにより、以下のようなコードを書くことができる。

struct Base { virtual void p() = 0 ; } ;
struct Cat : Base { } ;
struct Dog : Base { } ;

std::string get_name( Base & ref )
{
    decltype(auto) ti = typeid(ref) ;
    if ( ti == typeid(Cat) )
        return u8"猫" ;
    else if ( ti == typeid(Dog) )
        return u8"犬" ;

    return u8"謎" ;
}

もちろん、世間一般的に、この場合にはvirtual関数を使うのが礼儀正しい作法であるとされている。

struct Base { virtual std::string get_name() = 0 ; } ;
struct Cat : Base { std::string get_name() { return u8"猫" ; } } ;
struct Dog : Base { std::string get_name() { return u8"犬" ; } } ;


auto get_name( Base & ref )
{
    return ref.get_name() ;
}

さて、typeidの結果であるtype_info型は、かなり扱いづらい。すでに説明したように、type_infoはデフォルト構築、コピー、ムーブができず、typeid式から得られるオブジェクトを、リファレンスやポインター経由で参照して使うしかないからだ。これは、type_info型のオブジェクトの大量に管理したい場合に問題になる。オブジェクトを大量に管理するには、vectorやmapやunordered_mapなどのコンテナーを使いたいが、type_info型を直接使うわけには行かない。とすると、ポインターだろうか。

std::vector< std::type_info * > v ;

vectorならこれでよくても、やはり型情報という性質上、それぞれの型に何らかの値を付属させて、後から検索したい都合も出てくる。mapやunordered_mapで使いたい。

そのためには、type_info *型をラップするという手がある。しかし、正しくラップするのは、以下のように単調でめんどくさい。

namespace ezoe {

class type_index
{
public:
    type_index(const std::type_info& rhs) noexcept
        : target( &rhs ) { }
    bool operator==(const type_index& rhs) const noexcept
    { return *target == *rhs.target ; }
    bool operator!=(const type_index& rhs) const noexcept
    { return *target != *rhs.target ; }
    bool operator< (const type_index& rhs) const noexcept
    { return target->before( *rhs.target ) ; }
    bool operator<= (const type_index& rhs) const noexcept
    { return !target->before( *rhs.target ) ; }
    bool operator> (const type_index& rhs) const noexcept
    { return rhs.target->before( *target ) ; }
    bool operator>= (const type_index& rhs) const noexcept
    { return !rhs.target->before( *target) ; }
    std::size_t hash_code() const noexcept
    { return target->hash_code() ; }
    const char* name() const noexcept
    { return target->name() ; }
private:
    const std::type_info* target;
} ;

}

namespace std
{
    template < >
    struct hash< ezoe::type_index >
    {
        size_t operator() ( ezoe::type_index ti ) const noexcept
        {
            return ti.hash_code() ;
        }
    } ;
}

このコードは、誰が書いてもこのようになる。しかし、これを正しく書くのは面倒で、間違いやすく、しかもお互いに非互換なラッパーが乱立してしまう。そこで、このようなラッパー、std::type_indexが、C++11では標準ライブラリに追加された。使うには、ヘッダーファイルtypeindexをincludeする必要がある。

#include <typeindex>
#include <map>

int main()
{
    std::map< std::type_index, std::string > m =
    {
        { typeid(int), "int" },
        { typeid(double), "double"}
    } ;

    std::cout << m[typeid(int)] << '\n' ;
}

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

次に解説すべきC++11で追加されたライブラリを探している。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0