Retirando o resultado de std :: type_info :: name

93

Atualmente estou trabalhando em algum código de registro que supostamente - entre outras coisas - imprime informações sobre a função de chamada. Isso deve ser relativamente fácil, o C ++ padrão tem uma type_infoclasse. Contém o nome da classe / função / etc. mas está mutilado. Não é muito útil. Ou seja, typeid(std::vector<int>).name()retorna St6vectorIiSaIiEE.

Existe uma maneira de produzir algo útil a partir disso? Como std::vector<int>no exemplo acima. Se funcionar apenas para classes não-template, tudo bem também.

A solução deve funcionar para o gcc, mas seria melhor se eu pudesse portá-lo. É para registro, portanto, não é tão importante que não possa ser desativado, mas deve ser útil para depuração.

término
fonte

Respostas:

117

Dada a atenção que esta pergunta / resposta recebe e o feedback valioso do GManNickG , limpei um pouco o código. Duas versões são fornecidas: uma com recursos do C ++ 11 e outra com apenas recursos do C ++ 98.

No arquivo 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

No arquivo type.cpp (requer 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;
}

Ele imprime:

Tipo de ptr_base: Base*
Tipo de ponta:Derived

Testado com g ++ 4.7.2, g ++ 4.9.0 20140302 (experimental), clang ++ 3.4 (tronco 184647), clang 3.5 (tronco 202594) em Linux 64 bits e g ++ 4.7.2 (Mingw32, Win32 XP SP2).

Se você não pode usar os recursos do C ++ 11, veja como isso pode ser feito no C ++ 98, o arquivo type.cpp agora é:

#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


(Atualização de 8 de setembro de 2013)

A resposta aceita (em 7 de setembro de 2013) , quando a chamada para abi::__cxa_demangle()for bem-sucedida, retorna um ponteiro para um array local, com pilha alocada ... ai!
Observe também que se você fornecer um buffer, abi::__cxa_demangle()assume que ele está alocado no heap. Alocar o buffer na pilha é um bug (do documento gnu): "Se output_buffernão for longo o suficiente, ele é expandido usando realloc." Invocando realloc()um ponteiro para a pilha ... ai! (Veja também o comentário gentil de Igor Skochinsky .)

Você pode verificar facilmente esses dois bugs: basta reduzir o tamanho do buffer na resposta aceita (em 7 de setembro de 2013) de 1024 para algo menor, por exemplo 16, e dar a ele algo com um nome não superior a 15 (então realloc()é não chamado). Ainda assim, dependendo do seu sistema e das otimizações do compilador, a saída será: lixo / nada / travamento do programa.
Para verificar o segundo bug: defina o tamanho do buffer para 1 e chame-o com algo cujo nome seja maior que 1 caractere. Quando você o executa, o programa quase com certeza trava ao tentar chamar realloc()com um ponteiro para a pilha.


(A resposta antiga de 27 de dezembro de 2010)

Mudanças importantes feitas no código de KeithB : o buffer deve ser alocado por malloc ou especificado como NULL. NÃO o coloque na pilha.

É aconselhável verificar esse status também.

Eu não consegui encontrar HAVE_CXA_DEMANGLE. Eu verifico, __GNUG__embora isso não garanta que o código seja compilado. Alguém tem uma ideia melhor?

#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;
}
Todos
fonte
Observe que isso precisa #include <cxxabi.h>. Caso contrário, funcionou muito bem, obrigado.
Jterrace de
2
Dos documentos : output_bufferUma região de memória, alocada com malloc, de * bytes de comprimento, na qual o nome demangled é armazenado. Se output_buffer não for longo o suficiente, ele é expandido usando realloc. output_buffer pode ser NULL; nesse caso, o nome demangled é colocado em uma região de memória alocada com malloc.
Igor Skochinsky
2
@IgorSkochinsky Sim, ocorreu um erro de digitação em meu comentário anterior, mas não posso editá-lo. O que eu queria escrever: "A última vez que verifiquei abi::__cxa_demangleesperava que fosse alocado na pilha. " Muito obrigado por consultar o documento!
Ali
1
Observe que, tecnicamente, isso pode vazar se for ret_vallançado durante a construção. Você pode usar um protetor de escopo para se proteger contra isso.
GManNickG
3
Provavelmente seria mais claro usar std::unique_ptr<char, decltype(&std::free)>como assinatura para o ponteiro.
mindvirus
27

O núcleo do Boost contém um 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>
}

É basicamente um invólucro para abi::__cxa_demangle, como foi sugerido anteriormente.

moof2k
fonte
1
Se o impulso for uma opção, este é o melhor caminho a seguir!
hbobenicio
13

Isso é o que usamos. HAVE_CXA_DEMANGLE é definido apenas se disponível (apenas versões recentes do 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
fonte
6
Você precisa incluir #include <cxxabi.h>.
Fuenfundachtzig de
Interessante. Eu tenho __cxa_demangle sem HAVE_CXA_DEMANGLE definido
mkb
@Matt O que eu quis dizer é que nosso sistema de compilação, baseado em autoconf, só define HAVE_CXA_DEMANGLE se estiver disponível.
KeithB de
19
AVISO! O código acima provavelmente fará o programa travar. O buffer deve ser alocado por malloc ou especificado como NULL. NÃO o coloque na pilha. Veja meu código abaixo.
Ali
cuidado, res pode retornar NULL :)
Zibri
8

Aqui, dê uma olhada em type_strings.hpp, ele contém uma função que faz o que você deseja.

Se você apenas procura por uma ferramenta de demangling, que você pode usar, por exemplo, para desfigurar coisas mostradas em um arquivo de log, dê uma olhada em c++filt, que vem com binutils. Ele pode desmantelar nomes de símbolos C ++ e Java.

Johannes Schaub - litb
fonte
Apenas para observar, tanto cxa_demange () (que o código vinculado usa) e cx ++ filt são específicos do gcc. Não existe uma maneira portátil de fazer isso.
KeithB
c ++ filt não funciona, eu preciso dessas coisas (ou a maioria delas) em tempo de compilação, principalmente feito com macros.
término de
4
O link para type_strings.cpp parece quebrado.
StackedCrooked de
1
Olá @GregoryPakosz. O link do github em seu comentário acima também parece quebrado :( Saúde
olibre
Apenas um importante FYI: abi::__cxa_demangle()e sua laia <cxxabi.h> não são específicos do GCC - eles podem ter sido apenas GCC no passado distante, mas na época em que este post foi escrito, <cxxabi.h>era um padrão profundamente ad-hoc. Portanto, embora o link do código da resposta seja DOI, posso garantir que o Clang forneça suporte de primeira classe neste caso ... qv, da libcxxabifonte do Clang : o respectivo decl, impl, enorme teste: git.io/vRTBo , git.io/vRTBh , git.io/vRTRf - os comentários do código de teste observam a implementação do Clang como capaz de mais demangling, de alguma forma, em comparação com o GCC.
fish2000
4

É a implementação definida, portanto, não é algo que será portátil. No MSVC ++, name () é o nome não decorado, e você deve olhar raw_name () para obter o decorado.
Apenas uma facada no escuro aqui, mas sob gcc, você pode querer dar uma olhada em demangle.h

Eclipse
fonte
3

Não é uma solução completa, mas você pode querer dar uma olhada no que algumas das macros padrão (ou amplamente suportadas) definem. É comum no código de registro ver o uso das macros:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);
Lucas
fonte
4
Sem mencionar PRETTY_FUNCTION .
CesarB de
1
Isso lhe dará informações sobre onde você está no código. O que a pergunta estava fazendo era um nome bonito de um tipo, como std :: vector.
KeithB
Ele mencionou que era para depuração e eu afirmei que não era uma solução completa. Outras macros, como FUNCDNAME , retornarão o nome decorado.
luke
Na verdade, relendo a pergunta, era "No momento, estou trabalhando em algum código de registro que deveria - entre outras coisas - imprimir informações sobre a função de chamada." Isso funciona.
Max Lybbert
Não está completo, pois não conheço o namespace. Isso já está no meu código. Mas obrigado mesmo assim.
término de
3

Também encontrei uma macro chamada __PRETTY_FUNCTION__, que resolve o problema. Fornece um nome de função bonito (figuras :)). Isso é o que eu precisava.

Ou seja, me dá o seguinte:

virtual bool mutex::do_unlock()

Mas não acho que funcione em outros compiladores.

término
fonte
Sim, PRETTY_FUNCTION é específico do gcc.
Greg Rogers
2

Uma ligeira variação da solução de Ali. Se você quiser que o código ainda seja muito semelhante a

typeid(bla).name(),

escrevendo isso ao invés

Typeid(bla).name() (diferindo apenas na primeira letra maiúscula)

então você pode estar interessado nisto:

No arquivo 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 o mesmo da solução de Ali

matzzz
fonte
1

Dê uma olhada em __cxa_demangleque você pode encontrar em cxxabi.h.

CesarB
fonte
Eu peguei, está obsoleto, de acordo com a mensagem que recebo.
término de
Onde você encontrou essa mensagem? Eu apenas pesquisei no Google e parece ser compatível, nenhuma evidência de ter sido reprovado.
Ali
Talvez esteja obsoleto em :: namespace. Use abi :: __ cxa_demangle e você não receberá um aviso. Qual gcc você está 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
fonte
11
AVISO! O buffer deve ser alocado por malloc ou especificado como NULL. NÃO o coloque na pilha. Veja meu código abaixo.
Ali
1

A solução aceita [1] funciona quase sempre bem. Encontrei pelo menos um caso (e não o chamaria de caso secundário) em que não relata o que eu esperava ... com referências.

Para esses casos, encontrei outra solução, postada na parte inferior.

Caso problemático (usando typeconforme definido em [1]):

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

produz

Type of i is int
Type of ri is int

Solução (usando type_name<decltype(obj)>(), veja o código abaixo):

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

produz

Type of i is int
Type of ri is int&

como desejado (pelo menos por mim)

Código . Deve estar em um cabeçalho incluído, não em uma fonte compilada separadamente, devido a problemas de especialização. Consulte a referência indefinida para a função de modelo, por exemplo.

#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
fonte
0

Sempre quis usar type_info, mas tenho certeza de que o resultado da função de membro name () não é padrão e não retornará necessariamente nada que possa ser convertido em um resultado significativo.
Se você estiver usando apenas um compilador, talvez haja uma função específica do compilador que fará o que você quiser. Verifique a documentação.

Quamrana
fonte
0

Seguindo a solução de Ali, aqui está a alternativa de modelo C ++ 11 que funcionou melhor para o meu 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;
}

Irá imprimir:

double                                                                        
int                                                                           
test::SomeStruct
Alexis Paques
fonte