Démêler le résultat de std :: type_info :: name


93

Je travaille actuellement sur un code de journalisation censé, entre autres, imprimer des informations sur la fonction d'appel. Cela devrait être relativement simple, le C ++ standard a une type_infoclasse. Celui-ci contient le nom de la classe / fonction / etc de typeid'd. mais il est mutilé. Ce n'est pas très utile. Ie typeid(std::vector<int>).name()revient St6vectorIiSaIiEE.

Y a-t-il un moyen de produire quelque chose d'utile à partir de cela? Comme std::vector<int>pour l'exemple ci-dessus. Si cela ne fonctionne que pour les classes non modèles, c'est bien aussi.

La solution devrait fonctionner pour gcc, mais ce serait mieux si je pouvais la porter. C'est pour la journalisation, donc ce n'est pas si important qu'il ne puisse pas être désactivé, mais cela devrait être utile pour le débogage.

Réponses:


117

Compte tenu de l'attention que cette question / réponse reçoit et des précieux commentaires de GManNickG , j'ai un peu nettoyé le code. Deux versions sont proposées: une avec des fonctionnalités C ++ 11 et une autre avec uniquement des fonctionnalités C ++ 98.

Dans le fichier type.hpp

#ifndef TYPE_HPP
#define TYPE_HPP

#include <string>
#include <typeinfo>

std::string demangle(const char* name);

template <class T>
std::string type(const T& t) {

    return demangle(typeid(t).name());
}

#endif

Dans le fichier type.cpp (nécessite C ++ 11)

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

std::string demangle(const char* name) {

    int status = -4; // some arbitrary value to eliminate the compiler warning

    // enable c++11 by passing the flag -std=c++11 to g++
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };

    return (status==0) ? res.get() : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
    return name;
}

#endif

Usage:

#include <iostream>
#include "type.hpp"

struct Base { virtual ~Base() {} };

struct Derived : public Base { };

int main() {

    Base* ptr_base = new Derived(); // Please use smart pointers in YOUR code!

    std::cout << "Type of ptr_base: " << type(ptr_base) << std::endl;

    std::cout << "Type of pointee: " << type(*ptr_base) << std::endl;

    delete ptr_base;
}

Il imprime:

Type de ptr_base: Base*
Type de pointee:Derived

Testé avec g ++ 4.7.2, g ++ 4.9.0 20140302 (expérimental), clang ++ 3.4 (trunk 184647), clang 3.5 (trunk 202594) sous Linux 64 bits et g ++ 4.7.2 (Mingw32, Win32 XP SP2).

Si vous ne pouvez pas utiliser les fonctionnalités C ++ 11, voici comment cela peut être fait en C ++ 98, le fichier type.cpp est maintenant:

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

struct handle {
    char* p;
    handle(char* ptr) : p(ptr) { }
    ~handle() { std::free(p); }
};

std::string demangle(const char* name) {

    int status = -4; // some arbitrary value to eliminate the compiler warning

    handle result( abi::__cxa_demangle(name, NULL, NULL, &status) );

    return (status==0) ? result.p : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
    return name;
}

#endif


(Mise à jour du 8 septembre 2013)

La réponse acceptée (au 7 septembre 2013) , lorsque l'appel à abi::__cxa_demangle()réussit, renvoie un pointeur vers un tableau local alloué par pile ... aïe!
Notez également que si vous fournissez un tampon, abi::__cxa_demangle()suppose qu'il est alloué sur le tas. Allouer le tampon sur la pile est un bogue (de la doc gnu): "Si ce output_buffern'est pas assez long, il est développé avec realloc." Appel realloc()d'un pointeur vers la pile ... aïe! (Voir aussi Igor Skochinsky commentaire aimable d' .)

Vous pouvez facilement vérifier ces deux bogues: réduisez simplement la taille de la mémoire tampon dans la réponse acceptée (à partir du 7 septembre 2013) de 1024 à quelque chose de plus petit, par exemple 16, et donnez-lui quelque chose avec un nom ne dépassant pas 15 (ainsi realloc()est pas appelé). Pourtant, en fonction de votre système et des optimisations du compilateur, la sortie sera: garbage / rien / programme crash.
Pour vérifier le deuxième bogue: définissez la taille de la mémoire tampon sur 1 et appelez-la avec quelque chose dont le nom est plus long que 1 caractère. Lorsque vous l'exécutez, le programme se bloque presque assurément lorsqu'il tente d'appeler realloc()avec un pointeur vers la pile.


(L'ancienne réponse du 27 décembre 2010)

Modifications importantes apportées au code de KeithB : le tampon doit être soit alloué par malloc, soit spécifié comme NULL.Ne l'allouez PAS sur la pile.

Il est également sage de vérifier ce statut.

Je n'ai pas réussi à trouver HAVE_CXA_DEMANGLE. Je vérifie __GNUG__même si cela ne garantit pas que le code sera même compilé. Quelqu'un a une meilleure idée?

#include <cxxabi.h>

const string demangle(const char* name) {

    int status = -4;

    char* res = abi::__cxa_demangle(name, NULL, NULL, &status);

    const char* const demangled_name = (status==0)?res:name;

    string ret_val(demangled_name);

    free(res);

    return ret_val;
}

Notez que cela nécessite #include <cxxabi.h>. Sinon, a très bien fonctionné, merci.
jterrace

2
À partir de la documentation : output_bufferUne région de mémoire, allouée avec malloc, de * length octets, dans laquelle le nom démêlé est stocké. Si output_buffer n'est pas assez long, il est développé à l'aide de realloc. output_buffer peut à la place être NULL; dans ce cas, le nom démêlé est placé dans une région de mémoire allouée avec malloc.
Igor Skochinsky

2
@IgorSkochinsky Oui, il y a une faute de frappe dans mon commentaire précédent mais je ne peux pas le modifier. Ce que je voulais écrire: "La dernière fois que j'ai vérifié, je abi::__cxa_demanglem'attendais à ce qu'il soit alloué sur le tas. " Merci beaucoup d'avoir recherché le document!
Ali

1
Notez que techniquement, cela peut fuir en cas de ret_valprojection pendant la construction. Vous pouvez utiliser une lunette de protection pour vous en prémunir.
GManNickG

3
Si serait probablement plus clair à utiliser std::unique_ptr<char, decltype(&std::free)>comme signature pour votre pointeur.
mindvirus

27

Le noyau Boost contient un démêleur. Commander core / demangle.hpp :

#include <boost/core/demangle.hpp>
#include <typeinfo>
#include <iostream>

template<class T> struct X
{
};

int main()
{
    char const * name = typeid( X<int> ).name();

    std::cout << name << std::endl; // prints 1XIiE
    std::cout << boost::core::demangle( name ) << std::endl; // prints X<int>
}

C'est fondamentalement juste un wrapper pour abi::__cxa_demangle, comme cela a été suggéré précédemment.


1
Si le boost est une option, c'est la meilleure façon de procéder!
hbobenicio

13

C'est ce que nous utilisons. HAVE_CXA_DEMANGLE n'est défini que s'il est disponible (versions récentes de GCC uniquement).

#ifdef HAVE_CXA_DEMANGLE
const char* demangle(const char* name)
{
   char buf[1024];
    unsigned int size=1024;
    int status;
    char* res = abi::__cxa_demangle (name,
                                 buf,
                                 &size,
                                 &status);
    return res;
  }
#else
const char* demangle(const char* name)
{
  return name;
}
#endif  

6
Vous devez inclure #include <cxxabi.h>.
fuenfundachtzig

Intéressant. J'ai __cxa_demangle sans HAVE_CXA_DEMANGLE défini
mkb

@Matt Ce que je voulais dire, c'est que notre système de construction, basé sur autoconf, ne définit HAVE_CXA_DEMANGLE que s'il est disponible.
KeithB

19
AVERTISSEMENT! Le code ci-dessus est susceptible de faire planter le programme. Le tampon doit être soit alloué par malloc, soit spécifié comme NULL. Ne l'allouez PAS sur la pile. Voir mon code ci-dessous.
Ali

attention, res pourrait retourner NULL :)
Zibri

8

Ici, jetez un œil à type_strings.hpp, il contient une fonction qui fait ce que vous voulez.

Si vous cherchez simplement un outil de démêlage, que vous pourriez utiliser par exemple pour modifier les éléments affichés dans un fichier journal, jetez un œil à c++filt, qui est fourni avec binutils. Il peut démêler les noms de symboles C ++ et Java.


Juste à noter, cxa_demange () (que le code lié utilise) et cx ++ filt sont spécifiques à gcc. Il n'existe aucun moyen portable de le faire.
KeithB

c ++ filt ne le coupe pas, j'ai besoin de ce truc (ou de la plupart d'entre eux) au moment de la compilation, principalement avec des macros.
terminus

4
Le lien vers type_strings.cpp semble rompu.
StackedCrooked

1
Salut @GregoryPakosz Le lien github dans votre commentaire ci-dessus semble également cassé :( Cheers
olibre

Juste un FYI important: abi::__cxa_demangle()et ses semblables ne <cxxabi.h> sont pas spécifiques à GCC - ils peuvent avoir été GCC seulement dans un passé lointain, mais au moment de la rédaction de cet article, il <cxxabi.h>s'agissait d'une norme ad hoc enracinée. Donc, alors que le lien de code de la réponse était DOI, je peux garantir que Clang fournisse un support de première classe dans ce cas ... qv, de la libcxxabisource de Clang : le test respectivement decl, impl, énorme test: git.io/vRTBo , git.io/vRTBh , git.io/vRTRf - les commentaires du code de test indiquent que l'implémentation Clang est capable de plus de démêlage, d'une manière ou d'une autre, par rapport à GCC.
fish2000

4

C'est une implémentation définie, donc ce n'est pas quelque chose qui va être portable. Dans MSVC ++, name () est le nom non décoré, et vous devez regarder raw_name () pour obtenir celui décoré.
Juste un coup de couteau dans le noir ici, mais sous gcc, vous voudrez peut-être regarder demangle.h


3

Ce n'est pas une solution complète, mais vous voudrez peut-être regarder ce que définissent certaines des macros standard (ou largement prises en charge). Il est courant dans le code de journalisation de voir l'utilisation des macros:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);

4
Sans parler de PRETTY_FUNCTION .
CesarB

1
Cela vous donnera les informations sur votre position dans le code. Ce que la question posait était un joli nom d'un type, comme std :: vector.
KeithB

Il a mentionné que c'était pour le débogage, et j'ai déclaré que ce n'était pas une solution complète. D'autres macros telles que FUNCDNAME renverront le nom décoré.
luke

En fait, en relisant la question, c'était "Je travaille actuellement sur un code de journalisation qui est censé - entre autres - imprimer des informations sur la fonction d'appel." Cela marche.
Max Lybbert

Ce n'est pas complet, car je ne connais pas l'espace de noms. C'est déjà dans mon code. Mais merci quand même.
terminus

3

J'ai aussi trouvé une macro appelée __PRETTY_FUNCTION__ , qui fait l'affaire. Cela donne un joli nom de fonction (chiffres :)). C'est ce dont j'avais besoin.

Ie cela me donne ce qui suit:

virtual bool mutex::do_unlock()

Mais je ne pense pas que cela fonctionne sur d'autres compilateurs.


Oui, PRETTY_FUNCTION est spécifique à gcc.
Greg Rogers

2

Une légère variation sur la solution d'Ali. Si vous voulez que le code soit toujours très similaire à

typeid(bla).name(),

écrire ceci à la place

Typeid(bla).name() (ne différant que par la première lettre majuscule)

alors vous pourriez être intéressé par ceci:

Dans le fichier type.hpp

#ifndef TYPE_HPP
#define TYPE_HPP

#include <string>
#include <typeinfo>

std::string demangle(const char* name);

/*
template <class T>
std::string type(const T& t) {

  return demangle(typeid(t).name());
}
*/

class Typeid {
 public:

  template <class T>
    Typeid(const T& t) : typ(typeid(t)) {}

  std::string name() { return demangle(typ.name()); }

 private:
  const std::type_info& typ;
};


#endif

type.cpp reste le même que dans la solution d'Ali


1

Jetez un œil à __cxa_demanglece que vous pouvez trouver sur cxxabi.h.


J'ai pris, c'est obsolète, selon le message que je reçois.
terminus

Où avez-vous trouvé ce message? Je viens de googler et il semble être pris en charge, aucune preuve d'être obsolète.
Ali

Peut-être que c'est obsolète dans :: namespace. Utilisez abi :: __ cxa_demangle et vous n'obtiendrez pas d'avertissement. Quel gcc utilisez-vous?
onitake le

1
// KeithB's solution is good, but has one serious flaw in that unless buf is static
// it'll get trashed from the stack before it is returned in res - and will point who-knows-where
// Here's that problem fixed, but the code is still non-re-entrant and not thread-safe.
// Anyone care to improve it?

#include <cxxabi.h>

// todo: javadoc this properly
const char* demangle(const char* name)
{
    static char buf[1024];
    size_t size = sizeof(buf);
    int status;
    // todo:
    char* res = abi::__cxa_demangle (name,
                                 buf,
                                 &size,
                                 &status);
    buf[sizeof(buf) - 1] = 0; // I'd hope __cxa_demangle does this when the name is huge, but just in case.
    return res;
  }

11
AVERTISSEMENT! Le tampon doit être soit alloué par malloc, soit spécifié comme NULL. Ne l'allouez PAS sur la pile. Voir mon code ci-dessous.
Ali

1

La solution acceptée [1] fonctionne généralement bien. J'ai trouvé au moins un cas (et je ne l'appellerais pas un cas d'angle) où il ne rapporte pas ce que j'attendais ... avec des références.

Pour ces cas, j'ai trouvé une autre solution, affichée en bas.

Cas problématique (en utilisant typecomme défini dans [1]):

int i = 1;
cout << "Type of " << "i" << " is " << type(i) << endl;
int & ri = i;
cout << "Type of " << "ri" << " is " << type(ri) << endl;

produit

Type of i is int
Type of ri is int

Solution (en utilisant type_name<decltype(obj)>(), voir le code ci-dessous):

cout << "Type of " << "i" << " is " << type_name<decltype(i)>() << endl;
cout << "Type of " << "ri" << " is " << type_name<decltype(ri)>() << endl;

produit

Type of i is int
Type of ri is int&

comme désiré (au moins par moi)

Code . Il doit être dans un en-tête inclus, pas dans une source compilée séparément, en raison de problèmes de spécialisation. Voir la référence non définie à la fonction de modèle par exemple.

#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

0

J'ai toujours voulu utiliser type_info, mais je suis sûr que le résultat de la fonction membre name () n'est pas standard et ne retournera pas nécessairement tout ce qui peut être converti en un résultat significatif.
Si vous vous en tenez à un compilateur, il peut y avoir une fonction spécifique au compilateur qui fera ce que vous voulez. Consultez la documentation.


0

Suite à la solution d'Ali, voici l' alternative basée sur le modèle C ++ 11 qui fonctionnait le mieux pour mon utilisation.

// type.h
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

template <typename T>
std::string demangle() {
  int status = -4;

  std::unique_ptr<char, void (*)(void*)> res{
      abi::__cxa_demangle(typeid(T).name(), NULL, NULL, &status), std::free};
  return (status == 0) ? res.get() : typeid(T).name();
}

Usage:

// main.cpp
#include <iostream>

namespace test {
    struct SomeStruct {};
}

int main()
{
    std::cout << demangle<double>() << std::endl;
    std::cout << demangle<const int&>() << std::endl;
    std::cout << demangle<test::SomeStruct>() << std::endl;

    return 0;
}

Imprimera:

double                                                                        
int                                                                           
test::SomeStruct
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.