繁体   English   中英

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

[英]Unmangling the result of std::type_info::name

我目前正在编写一些日志代码,这些代码应该 - 除其他外 - 打印有关调用函数的信息。 这应该比较容易,标准 C++ 有一个type_info类。 这包含 typeid 的类/函数/等的名称。 但它被破坏了。 它不是很有用。 typeid(std::vector<int>).name()返回St6vectorIiSaIiEE

有没有办法从中产生有用的东西? 就像上面例子的std::vector<int>一样。 如果它只适用于非模板类,那也没关系。

该解决方案应该适用于 gcc,但如果我可以移植它会更好。 它是用于记录的,所以它不是很重要,不能关闭,但它应该有助于调试。

鉴于这个问题/答案受到的关注,以及来自GManNickG的宝贵反馈,我已经稍微清理了代码。 给出了两个版本:一个具有 C++11 特性,另一个具有 C++98 特性。

在文件类型.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*
指针类型: Derived

在 Linux 64 位和 g++ 4.7.2(Mingw32、Win32 XP SP2)上使用 g++ 4.7.2、g++ 4.9.0 20140302(实验性)、clang++ 3.4(主干 184647)、clang 3.5(主干 202594)进行了测试。

如果你不能使用 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 文档): “如果output_buffer不够长,则使用realloc对其进行扩展。” 在指向堆栈的指针上调用realloc() ...哎呀! (另见Igor Skochinsky的善意评论。)

您可以轻松验证这两个错误:只需将已接受答案(截至 2013 年 9 月 7 日)中的缓冲区大小从 1024 减少到更小的值,例如 16,并为其命名超过 15 的名称(因此realloc()被调用)。 尽管如此,根据您的系统和编译器优化,输出将是:垃圾/无/程序崩溃。
要验证第二个错误:将缓冲区大小设置为 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;
}

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的包装器。

如果我们想要的只是用于记录的未修改类型名称,我们实际上可以在不使用std::type_info甚至 RTTI 的情况下做到这一点。

适用于三大主要编译器前端( )的稍微可移植的解决方案是使用函数template并从函数名称中提取类型名称。

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

  • gcc被格式化为void foo() [with T = int; ] 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>

在铿锵声将输出:

std::basic_string<char>

现场示例


这种避免prefixsuffix的方法的类似增强是假设函数名称对于所有类型都是相同的,并搜索哨兵类型以解析出从每一端到哨兵的偏移量。 这确保了字符串搜索只发生一次,并且假定偏移量每次都找到字符串名称。 例如,使用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 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__的输出的副作用。

这就是我们使用的。 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  

在这里,看看type_strings.hpp它包含一个函数,可以满足你的需求。

如果您只是在寻找一个去处理工具,例如可以用来处理日志文件中显示的内容,请查看 binutils 附带的c++filt 它可以分解 C++ 和 Java 符号名称。

它是实现定义的,所以它不是可移植的。 在 MSVC++ 中,name() 是未修饰的名称,您必须查看 raw_name() 才能获得修饰的名称。
在这里只是在黑暗中刺伤,但在 gcc 下,您可能想查看demangle.h

我还发现了一个名为__PRETTY_FUNCTION__的宏,它可以解决问题。 它给出了一个漂亮的函数名称(数字:))。 这就是我需要的。

即它给了我以下信息:

virtual bool mutex::do_unlock()

但我认为它不适用于其他编译器。

公认的解决方案[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&

根据需要(至少由我)

代码 由于专业化问题,它必须在包含的标头中,而不是在单独编译的源中。 例如,请参阅对模板函数的未定义引用

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

不是一个完整的解决方案,但您可能想查看一些标准(或广泛支持的)宏的定义。 在记录代码中看到宏的使用是很常见的:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);

阿里的解决方案略有不同。 如果您希望代码仍然非常相似

typeid(bla).name() ,

改为写这个

Typeid(bla).name() (仅首字母大写不同)

那么您可能对此感兴趣:

在文件类型.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与阿里解决方案保持一致

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

看看你可以在__cxa_demangle找到的cxxabi.h

遵循 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

我一直想使用 type_info,但我确信 name() 成员函数的结果是非标准的,不一定会返回任何可以转换为有意义的结果的东西。
如果您坚持使用一个编译器,那么可能会有一个特定于编译器的函数可以满足您的需求。 检查文档。

boost::typeindex提供了一些有用的东西。

#include <boost/type_index.hpp>
#include <iostream>
#include <vector>

class Widget {};

int main() {
  using boost::typeindex::type_id_with_cvr;
  const std::vector<Widget> vw;
  std::cout << type_id_with_cvr<decltype(vw)>().pretty_name() << std::endl;
  std::cout << type_id_with_cvr<decltype(vw[0])>().pretty_name() << std::endl;
  return 0;
}

输出是

std::vector<Widget, std::allocator<Widget> > const
Widget const&

值得注意的是type_id_with_cvr保留了引用和 c/v 限定符,而typeid没有。 请参见以下示例:

#include <iostream>
#include <boost/type_index.hpp>
#include <typeindex>
#include <vector>
#include <typeinfo>

class Widget {};

template <typename T>
void f(const T &param) {
  std::cout << typeid(param).name() << std::endl;
  std::cout
      << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name()
      << std::endl;
}

int main() {
  const std::vector<Widget> vw(1);
  f(&vw[0]);
  return 0;
}

输出是

PK6Widget
Widget const* const&

在这里, typeid产生PK6Widget ,这意味着指向K onst Widget 的指针。 数字“6”是名称“Widget”的长度。 这不是param的正确类型,其中删除了引用和 const 限定符。

type_id_with_cvr实际上使用了boost::core中的解组函数,正如这个答案中所提到的。 为了保留 cv 限定符或引用,它只定义了一个名为cvr_saver的空模板,然后将cvr_saver<type>传递给typeid

Effective Modern C++ Item 4 谈到了这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM