Desarmando el resultado de std :: type_info :: name

93

Actualmente estoy trabajando en un código de registro que supuestamente, entre otras cosas, imprime información sobre la función de llamada. Esto debería ser relativamente fácil, C ++ estándar tiene una type_infoclase. Contiene el nombre de la clase / función / etc. pero está destrozado. No es muy útil. Es decir, typeid(std::vector<int>).name()vuelve St6vectorIiSaIiEE.

¿Hay alguna forma de producir algo útil a partir de esto? Como std::vector<int>en el ejemplo anterior. Si solo funciona para clases que no son de plantilla, también está bien.

La solución debería funcionar para gcc, pero sería mejor si pudiera portarla. Es para el registro, por lo que no es tan importante que no se pueda apagar, pero debería ser útil para la depuración.

término
fuente

Respuestas:

117

Dada la atención que recibe esta pregunta / respuesta y los valiosos comentarios de GManNickG , he limpiado un poco el código. Se dan dos versiones: una con características de C ++ 11 y otra con solo características de C ++ 98.

En archivo 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

En el archivo type.cpp (requiere 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

Uso:

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

Imprime:

Tipo de ptr_base: Base*
Tipo de puntero:Derived

Probado con g ++ 4.7.2, g ++ 4.9.0 20140302 (experimental), clang ++ 3.4 (troncal 184647), clang 3.5 (troncal 202594) en Linux de 64 bits y g ++ 4.7.2 (Mingw32, Win32 XP SP2).

Si no puede usar las funciones de C ++ 11, así es como se puede hacer en C ++ 98, el archivo type.cpp es ahora:

#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


(Actualización del 8 de septiembre de 2013)

La respuesta aceptada (a partir del 7 de septiembre de 2013) , cuando la llamada a abi::__cxa_demangle()es exitosa, devuelve un puntero a una matriz local asignada a la pila ... ¡ay!
También tenga en cuenta que si proporciona un búfer, se abi::__cxa_demangle()supone que está asignado en el montón. Asignar el búfer en la pila es un error (del gnu doc): "Si output_bufferno es lo suficientemente largo, se expande usando realloc". Invocando realloc()un puntero a la pila ... ¡ay! (Véase también el amable comentario de Igor Skochinsky ).

Puede verificar fácilmente estos dos errores: simplemente reduzca el tamaño del búfer en la respuesta aceptada (a partir del 7 de septiembre de 2013) de 1024 a algo más pequeño, por ejemplo 16, y asígnele algo con un nombre que no sea más largo que 15 (también lo realloc()es no llamado). Aún así, dependiendo de su sistema y las optimizaciones del compilador, el resultado será: basura / nada / bloqueo del programa.
Para verificar el segundo error: establezca el tamaño del búfer en 1 y llámelo con algo cuyo nombre tenga más de 1 carácter. Cuando lo ejecuta, es casi seguro que el programa se bloquee cuando intenta llamar realloc()con un puntero a la pila.


(La respuesta anterior del 27 de diciembre de 2010)

Cambios importantes realizados en el código de KeithB : el búfer debe ser asignado por malloc o especificado como NULL. NO lo asigne a la pila.

También es aconsejable comprobar ese estado.

No pude encontrar HAVE_CXA_DEMANGLE. Verifico, __GNUG__aunque eso no garantiza que el código incluso se compile. ¿Alguien tiene una idea mejor?

#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;
}
Ali
fuente
Tenga en cuenta que esto necesita #include <cxxabi.h>. De lo contrario funcionó muy bien, gracias.
Terraza
2
De los documentos : output_bufferuna región de memoria, asignada con malloc, de * bytes de longitud, en la que se almacena el nombre demandado. Si output_buffer no es lo suficientemente largo, se expande usando realloc. output_buffer en su lugar puede ser NULL; en ese caso, el nombre demandado se coloca en una región de memoria asignada con malloc.
Igor Skochinsky
2
@IgorSkochinsky Sí, hay un error tipográfico en mi comentario anterior, pero no puedo editarlo. Lo que quería escribir: "La última vez que verifiqué abi::__cxa_demangleesperaba que se asignara en el montón " . ¡Muchas gracias por buscar el documento!
Ali
1
Tenga en cuenta que técnicamente esto puede filtrarse si se ret_vallanza durante la construcción. Puede usar un protector de alcance para protegerse contra eso.
GManNickG
3
Probablemente sería más claro usarlo std::unique_ptr<char, decltype(&std::free)>como firma para su puntero.
Mindvirus
27

Boost core contiene un demangler. Checkout 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>
}

Básicamente es solo un contenedor para abi::__cxa_demangle, como se sugirió anteriormente.

moof2k
fuente
1
Si el impulso es una opción, ¡esta es la mejor manera de hacerlo!
hbobenicio
13

Esto es lo que usamos. HAVE_CXA_DEMANGLE solo se establece si está disponible (solo versiones recientes de GCC).

#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  
KeithB
fuente
6
Necesitas incluir #include <cxxabi.h>.
fuenfundachtzig
Interesante. Tengo __cxa_demangle sin HAVE_CXA_DEMANGLE definido
mkb
@Matt Lo que quise decir es que nuestro sistema de compilación, basado en autoconf, solo establece HAVE_CXA_DEMANGLE si está disponible.
KeithB
19
¡ADVERTENCIA! Es probable que el código anterior haga que el programa se bloquee. El búfer debe ser asignado por malloc o especificado como NULL. NO lo asigne a la pila. Vea mi código a continuación.
Ali
cuidado, res podría devolver NULL :)
Zibri
8

Aquí, eche un vistazo a type_strings.hpp , contiene una función que hace lo que quiere.

Si solo busca una herramienta de demanda, que, por ejemplo, podría usar para modificar las cosas que se muestran en un archivo de registro, eche un vistazo c++filt, que viene con binutils. Puede exigir nombres de símbolos de C ++ y Java.

Johannes Schaub - litb
fuente
Solo para tener en cuenta, tanto cxa_demange () (que utiliza el código vinculado) como cx ++ filt son específicos de gcc. No hay una forma portátil de hacer esto.
KeithB
c ++ filt no funciona, necesito estas cosas (o la mayor parte) en tiempo de compilación, en su mayoría hechas con macros.
terminal
4
El enlace a type_strings.cpp parece roto.
StackedCrooked
1
Hola @GregoryPakosz El enlace de github en tu comentario anterior también parece roto :( Saludos
olibre
Solo un FYI importante: abi::__cxa_demangle()y los de su tipo <cxxabi.h> no son específicos de GCC ; es posible que hayan sido solo de GCC en el pasado distante, pero en el momento de redactar este artículo, <cxxabi.h>era un estándar ad-hoc arraigado. Entonces, si bien el enlace del código de la respuesta era DOI, puedo garantizar que Clang proporcione soporte de primera clase en este caso ... qv, de la libcxxabifuente de Clang : el respectivo decl, impl, enorme prueba: git.io/vRTBo , git.io/vRTBh , git.io/vRTRf : los comentarios del código de prueba señalan que la implementación de Clang es capaz de exigir más , de alguna manera, que GCC.
fish2000
4

Está definida por la implementación, por lo que no es algo que sea portátil. En MSVC ++, name () es el nombre sin decorar, y tienes que mirar raw_name () para obtener el decorado.
Solo una puñalada en la oscuridad aquí, pero debajo de gcc, es posible que desee ver demangle.h

Eclipse
fuente
3

No es una solución completa, pero es posible que desee ver lo que definen algunas de las macros estándar (o ampliamente admitidas). Es común en el código de registro ver el uso de las macros:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);
lucas
fuente
4
Sin mencionar PRETTY_FUNCTION .
CesarB
1
Esto le dará la información sobre dónde se encuentra en el código. Lo que preguntaba era un bonito nombre de un tipo, como std :: vector.
KeithB
Mencionó que era para depurar y yo dije que no era una solución completa. Otras macros como FUNCDNAME devolverán el nombre decorado.
luke
En realidad, releyendo la pregunta, era "Actualmente estoy trabajando en un código de registro que supuestamente, entre otras cosas, imprime información sobre la función de llamada". Esto funciona.
Max Lybbert
No está completo, ya que no conozco el espacio de nombres. Esto ya está en mi código. Pero gracias de todos modos.
terminal
3

También encontré una macro llamada __PRETTY_FUNCTION__, que hace el truco. Da un bonito nombre de función (cifras :)). Esto es lo que necesitaba.

Es decir, me da lo siguiente:

virtual bool mutex::do_unlock()

Pero no creo que funcione en otros compiladores.

término
fuente
Sí, PRETTY_FUNCTION es específico de gcc.
Greg Rogers
2

Una ligera variación de la solución de Ali. Si desea que el código siga siendo muy similar a

typeid(bla).name(),

escribiendo esto en su lugar

Typeid(bla).name() (difiere solo en la primera letra mayúscula)

entonces te puede interesar esto:

En archivo 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 permanece igual que en la solución de Ali

matzzz
fuente
1

Eche un vistazo a __cxa_demanglelo que puede encontrar en cxxabi.h.

CesarB
fuente
Tomé, está en desuso, según el mensaje que recibo.
terminal
¿Dónde encontraste ese mensaje? Lo busqué en Google y parece ser compatible, no hay evidencia de que esté desaprobado.
Ali
Quizás esté obsoleto en :: espacio de nombres. Utilice abi :: __ cxa_demangle y no recibirá ninguna advertencia. ¿Qué gcc estás usando?
onitake
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;
  }
Dan Dare
fuente
11
¡ADVERTENCIA! El búfer debe ser asignado por malloc o especificado como NULL. NO lo asigne a la pila. Vea mi código a continuación.
Ali
1

La solución aceptada [1] funciona principalmente bien. Encontré al menos un caso (y no lo llamaría un caso de esquina) donde no informa lo que esperaba ... con referencias.

Para esos casos, encontré otra solución, publicada en la parte inferior.

Caso problemático (utilizando typecomo se define en [1]):

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

produce

Type of i is int
Type of ri is int

Solución (usando type_name<decltype(obj)>(), vea el código a continuación):

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

produce

Type of i is int
Type of ri is int&

como desee (al menos por mí)

Codigo . Tiene que estar en un encabezado incluido, no en una fuente compilada por separado, debido a problemas de especialización. Consulte la referencia no definida a la función de plantilla, por ejemplo.

#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;
}
sancho.s ReinstateMonicaCellio
fuente
0

Siempre quise usar type_info, pero estoy seguro de que el resultado de la función miembro name () no es estándar y no necesariamente devolverá nada que pueda convertirse en un resultado significativo.
Si se apega a un compilador, tal vez haya una función específica del compilador que hará lo que desee. Consulta la documentación.

quamrana
fuente
0

Siguiendo la solución de Ali, aquí está la alternativa con plantilla de C ++ 11 que funcionó mejor para mi uso.

// 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();
}

Uso:

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

Imprimirá:

double                                                                        
int                                                                           
test::SomeStruct
Alexis Paques
fuente