解析 std::type_info::name 的结果

129

目前我正在编写一些日志记录代码,其中应该包括打印有关调用函数的信息。这应该相对容易,标准C++中有一个type_info类,其中包含了typeid'd类/函数等的名称,但它是被修饰过的。它并不是很有用。例如,typeid(std::vector<int>).name()返回St6vectorIiSaIiEE

是否有一种方法可以从中产生出一些有用的东西?比如上面的例子返回std::vector<int>。如果仅适用于非模板类,则也可以。

解决方法应该适用于gcc,但最好能够移植。这是为了日志记录,所以关闭它并不那么重要,但它应该对调试有所帮助。

16个回答

145

鉴于这个问题/答案受到的关注,以及GManNickG提供的宝贵反馈,我稍微整理了一下代码。提供了两个版本:一个使用C++11功能,另一个只使用C++98功能。

在文件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

在文件 type.cpp 中(需要 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

使用方法:

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

它会打印:

ptr_base 的类型:Base*
pointee 的类型:Derived

在 Linux 64 位上使用 g++ 4.7.2、g++ 4.9.0 20140302(实验版)、clang++ 3.4(trunk 184647)、clang 3.5(trunk 202594)以及在 g++ 4.7.2(Mingw32,Win32 XP SP2)上进行了测试。

如果您无法使用 C++11 特性,则可以使用以下方法在 C++98 中完成,文件 type.cpp 现在为:

#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

(2013年9月8日更新)

被接受的答案(截至2013年9月7日)在调用abi::__cxa_demangle()成功时,返回一个指向本地的、堆栈分配的数组……疼!
还要注意,如果提供了缓冲区,abi::__cxa_demangle()会认为它是在堆上分配的。在堆栈上分配缓冲区是一个错误(来自gnu doc):“如果output_buffer不够长,它会使用realloc扩展。”在指向堆栈的指针上调用realloc()……疼!(请参见Igor Skochinsky的友善评论。)

你可以很容易地验证这两个bug:只需将被接受的答案(截至2013年9月7日)中的缓冲区大小从1024减小到更小的值,例如16,并给它一个名字超过15个字符(因此不会调用realloc())。不过,根据你的系统和编译器优化,输出可能是:垃圾/无/程序崩溃。
要验证第二个bug:将缓冲区大小设置为1,并用一个名字超过1个字符的东西调用它。当你运行它时,程序几乎肯定会崩溃,因为它试图用指向堆栈的指针调用realloc()


(2010年12月27日的旧答案)

KeithB的代码做出了重要的更改:缓冲区必须由malloc分配或指定为NULL。不要在堆栈上分配它。

智慧的做法是也检查一下状态。

我没能找到HAVE_CXA_DEMANGLE。我检查了__GNUG__,尽管这并不能保证代码能编译。有没有更好的主意?

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

2
文档中得知:output_buffer是一个内存区域,使用malloc分配,长度为*length字节,用于存储解码后的名称。如果output_buffer不够长,则使用realloc进行扩展。output_buffer也可以为NULL;在这种情况下,解码后的名称将放置在使用malloc分配的内存区域中。 - Igor Skochinsky
2
@IgorSkochinsky,我的上一条评论中有一个错别字,但我无法编辑它。我想写的是:“上次我检查时,abi::__cxa_demangle 期望它在堆上分配。”非常感谢您查找文档! - Ali
1
请注意,如果在构造期间ret_val抛出异常,这可能会导致泄漏。您可以使用作用域保护来防止这种情况发生。 - GManNickG
4
你可以使用std::unique_ptr<char, decltype(&std::free)>作为指针的签名,这样可能会更清晰明了。 - mindvirus
@mindvirus 是的,谢谢,我看到了你早先的编辑请求。但是,我不赞成这个改变:在类似情况下使用&tolower(::tolowerstd::tolower)时,我曾经遇到过一个非常奇怪的编译错误。这很可能是编译器的问题,但除非你能保证所有编译器在所有情况下都正确处理decltype(&std::free),否则我会保留它原样;回答中的版本将正常工作。 - Ali
显示剩余12条评论

40

Boost核心包含一个解码器。请查看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>
}

正如之前建议的那样,它基本上只是abi::__cxa_demangle的一个包装器。


3
如果可以使用boost库,这是最好的选择! - hbobenicio

19
如果我们只是想要用于日志记录的未修改类型名称,实际上我们可以在不使用std::type_info甚至RTTI的情况下完成这个任务。对于三个主要编译器前端(),一个稍微可移植的解决方案是使用函数模板并从函数名中提取类型名称。

gccclang 都提供了__PRETTY_FUNCTION__,它是当前函数或函数模板的名称,并在字符串中包含所有类型参数。类似地,MSVC有等效的__FUNCSIG__。每个编译器格式略有不同,例如对于调用void foo<int>,编译器将输出不同的内容:

  • gcc 格式为void foo() [with T = int; ]
  • clang 格式为void foo() [T = int]
  • msvc 格式为void foo<int>()

知道这些后,只需解析前缀和后缀并将其包装成一个函数,即可提取出类型名称。

我们甚至可以使用std::string_view和扩展的constexpr编译时通过解析模板函数名称来获取字符串名称。这也可以在任何早期版本的C++中完成,但仍需要某种形式的字符串解析。
例如:
#include <string_view>

template <typename T>
constexpr auto get_type_name() -> std::string_view
{
#if defined(__clang__)
    constexpr auto prefix = std::string_view{"[T = "};
    constexpr auto suffix = "]";
    constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
    constexpr auto prefix = std::string_view{"with T = "};
    constexpr auto suffix = "; ";
    constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
    constexpr auto prefix = std::string_view{"get_type_name<"};
    constexpr auto suffix = ">(void)";
    constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif

    const auto start = function.find(prefix) + prefix.size();
    const auto end = function.find(suffix);
    const auto size = end - start;

    return function.substr(start, size);
}

使用此方法,您可以调用get_type_name<T>()在编译时获取一个表示未加密类型名称的std::string_view
例如:
std::cout << get_type_name<std::string>() << std::endl;

在GCC上将输出:

std::__cxx11::basic_string<char>

而在clang上将输出:

std::basic_string<char>

实时示例


这种方法的类似增强版避免了前缀和后缀,假定所有类型的函数名相同,并搜索一个哨兵类型以从每个端点解析出哨兵的偏移量。这确保字符串搜索仅发生一次,并且偏移量被假定为每次查找字符串名称的位置。例如,使用double作为一个简单的哨兵:
template <typename T>
constexpr auto full_function_name() -> std::string_view
{
#if defined(__clang__) || defined(__GNUC__)
    return std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
    return std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif
}

// Outside of the template so its computed once
struct type_name_info {
    static constexpr auto sentinel_function = full_function_name<double>();
    static constexpr auto prefix_offset = sentinel_function.find("double");
    static constexpr auto suffix_offset = sentinel_function.size() - prefix_offset - /* strlen("double") */ 6;
};

template <typename T>
constexpr auto get_type_name() -> std::string_view
{
    constexpr auto function = full_function_name<T>();

    const auto start = type_name_info::prefix_offset;
    const auto end = function.size() - type_name_info::suffix_offset;
    const auto size = end - start;

    return function.substr(start, size);
}

实时例子


这段代码并不适用于所有编译器,但可以修改以适应任何提供__FUNCSIG__/__PRETTY_FUNCTION__等价物的编译器; 只需要进行一些解析即可。
注意:这段代码没有经过完全测试,可能存在一些错误;但主要思想是解析包含名称的全部输出--这通常是类似于__func__的编译器输出的副作用。

4
遗憾的是,即使在2021年,为了获得C++符号重整的效果,仍需要大量的样板代码 :-( - usr1234567
6
同意!希望 C++23 能够最终纳入静态反射支持,这样人们就不必依赖这种笨拙的方法了。 - Human-Compiler
在您提供的代码中,suffix_offset 值计算不正确。还需要添加字符串 "double" 的长度,或更简单地用 sentinel_function.size() - prefix_offset - /* strlen("double") */ 6 替换整个表达式。 - Alexey Ismagilov
@AlexeyIsmagilov 很好的发现,谢谢! - Human-Compiler
请注意,MSVC宏应该是“_MSC_VER”,而不是“__MSC_VER”。 - PikalaxALT

11

请看一下type_strings.hpp,其中包含了一种能够实现你所需要的功能的函数。

如果你只是在寻找一个反混淆工具,例如可以用于解析日志文件中的混淆字符串,请查看binutils附带的c++filt。它可以反混淆C++和Java符号名称。


1
请注意,cxa_demange()(链接到的代码使用)和cx++filt都是gcc特定的。没有可移植的方法来做到这一点。 - KeithB
c++filt 不够用,我需要这些东西(或大部分)在编译时完成,主要通过宏来实现。 - terminus
4
type_strings.cpp 的链接似乎已经失效。 - StackedCrooked
1
嗨@GregoryPakosz,您上面评论中的Github链接似乎也无法访问:( 祝好 - oHo
重要提醒:abi::__cxa_demangle()及其相关函数来自<cxxabi.h>,_并非仅适用于GCC_ - 它们可能在过去只适用于GCC,但在本文撰写时,<cxxabi.h>已成为一种根深蒂固的临时标准。因此,虽然答案中的代码链接是DOI,但我可以证明,在这种情况下,Clang提供了一流的支持...请参见Clang的libcxxabi源代码:相应的声明、实现、大型测试:http://git.io/vRTBo,http://git.io/vRTBh,http://git.io/vRTRf - 测试代码的注释指出,与GCC相比,Clang实现能够进行更多的解缠绕操作。 - fish2000

11

这是我们使用的。只有当可用时才会设置HAVE_CXA_DEMANGLE(仅适用于较新版本的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  

6
你需要加入 #include <cxxabi.h> - fuenfundachtzig
有趣。我没有定义HAVE_CXA_DEMANGLE,但是有__cxa_demangle函数。 - Matt K
@Matt 我的意思是,我们基于autoconf的构建系统只有在它可用时才设置HAVE_CXA_DEMANGLE。 - KeithB
22
警告!上述代码很可能会导致程序崩溃。缓冲区必须由malloc分配或指定为NULL。不要在堆栈上分配它。请参见下面的代码示例。 - Ali
1
小心,res 可能会返回 NULL :) - Zibri

5

它是实现定义的,因此不会具有可移植性。在MSVC++中,name()是未装饰的名称,您必须查看raw_name()以获取已装饰的名称。
这只是一个猜测,在gcc下,您可能需要查看demangle.h


4
我还发现了一个叫做__PRETTY_FUNCTION__的宏,它能够解决问题。它提供了一个漂亮的函数名称(很棒吧 :))。这正是我所需要的。
即它给出了以下结果:
virtual bool mutex::do_unlock()

但我认为它在其他编译器上不起作用。


1
是的,PRETTY_FUNCTION 是gcc特有的。 - Greg Rogers

3

被接受的解决方案 [1] 大多数情况下都有效。

我至少发现了一个情况(我不会称之为边角情况),在这种情况下它没有报告我所期望的结果...带有参考资料。

对于这些情况,我找到了另一个解决方案,发布在底部。

问题情况(使用[1]中定义的type):

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

产生

Type of i is int
Type of ri is int

解决方案(使用type_name<decltype(obj)>(),请参见下面的代码):

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

产生

Type of i is int
Type of ri is int&

按照我的要求(至少是我希望如此)

代码 。 由于特化问题,它必须在包含的头文件中,而不是在单独编译的源文件中。例如,请参见undefined reference to template function

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

2

对Ali的解决方案进行了轻微改动。如果你希望代码仍然与

typeid(bla).name()

非常相似,只是将其写成

Typeid(bla).name() (仅在首字母大小写上有所不同)

那么你可能会对这个感兴趣:

在文件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 与 Ali 的解决方案相同


2

跟随Ali的解决方案,这里是我使用最好的模板化替代方案C++11

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

使用方法:

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

将会打印:

double                                                                        
int                                                                           
test::SomeStruct

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接