简体   繁体   English

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

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

I'm currently working on some logging code that supposed to - among other things - print information about the calling function.我目前正在编写一些日志代码,这些代码应该 - 除其他外 - 打印有关调用函数的信息。 This should be relatively easy, standard C++ has a type_info class.这应该比较容易,标准 C++ 有一个type_info类。 This contains the name of the typeid'd class/function/etc.这包含 typeid 的类/函数/等的名称。 but it's mangled.但它被破坏了。 It's not very useful.它不是很有用。 Ie typeid(std::vector<int>).name() returns St6vectorIiSaIiEE .typeid(std::vector<int>).name()返回St6vectorIiSaIiEE

Is there a way to produce something useful from this?有没有办法从中产生有用的东西? Like std::vector<int> for the above example.就像上面例子的std::vector<int>一样。 If it only works for non-template classes, that's fine too.如果它只适用于非模板类,那也没关系。

The solution should work for gcc, but it would be better if I could port it.该解决方案应该适用于 gcc,但如果我可以移植它会更好。 It's for logging so it's not so important that it can't be turned off, but it should be helpful for debugging.它是用于记录的,所以它不是很重要,不能关闭,但它应该有助于调试。

Given the attention this question / answer receives, and the valuable feedback from GManNickG , I have cleaned up the code a little bit.鉴于这个问题/答案受到的关注,以及来自GManNickG的宝贵反馈,我已经稍微清理了代码。 Two versions are given: one with C++11 features and another one with only C++98 features.给出了两个版本:一个具有 C++11 特性,另一个具有 C++98 特性。

In file type.hpp在文件类型.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

In file type.cpp (requires C++11)在文件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

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

It prints:它打印:

Type of ptr_base: Base* ptr_base 的类型: Base*
Type of pointee: Derived指针类型: Derived

Tested with g++ 4.7.2, g++ 4.9.0 20140302 (experimental), clang++ 3.4 (trunk 184647), clang 3.5 (trunk 202594) on Linux 64 bit and g++ 4.7.2 (Mingw32, Win32 XP SP2).在 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)进行了测试。

If you cannot use C++11 features, here is how it can be done in C++98, the file type.cpp is now:如果你不能使用 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


(Update from Sep 8, 2013) (2013 年 9 月 8 日更新)

The accepted answer (as of Sep 7, 2013) , when the call to abi::__cxa_demangle() is successful, returns a pointer to a local, stack allocated array ... ouch!接受的答案(截至 2013 年 9 月 7 日) ,当对abi::__cxa_demangle()的调用成功时,返回一个指向本地堆栈分配数组的指针……哎呀!
Also note that if you provide a buffer, abi::__cxa_demangle() assumes it to be allocated on the heap.另请注意,如果您提供缓冲区, abi::__cxa_demangle()假定它是在堆上分配的。 Allocating the buffer on the stack is a bug (from the gnu doc): "If output_buffer is not long enough, it is expanded using realloc ."在堆栈上分配缓冲区是一个错误(来自 gnu 文档): “如果output_buffer不够长,则使用realloc对其进行扩展。” Calling realloc() on a pointer to the stack ... ouch!在指向堆栈的指针上调用realloc() ...哎呀! (See also Igor Skochinsky 's kind comment.) (另见Igor Skochinsky的善意评论。)

You can easily verify both of these bugs: just reduce the buffer size in the accepted answer (as of Sep 7, 2013) from 1024 to something smaller, for example 16, and give it something with a name not longer than 15 (so realloc() is not called).您可以轻松验证这两个错误:只需将已接受答案(截至 2013 年 9 月 7 日)中的缓冲区大小从 1024 减少到更小的值,例如 16,并为其命名超过 15 的名称(因此realloc()被调用)。 Still, depending on your system and the compiler optimizations, the output will be: garbage / nothing / program crash.尽管如此,根据您的系统和编译器优化,输出将是:垃圾/无/程序崩溃。
To verify the second bug: set the buffer size to 1 and call it with something whose name is longer than 1 character.要验证第二个错误:将缓冲区大小设置为 1,并使用名称超过 1 个字符的名称调用它。 When you run it, the program almost assuredly crashes as it attempts to call realloc() with a pointer to the stack.当你运行它时,程序几乎肯定会崩溃,因为它尝试使用指向堆栈的指针调用realloc()


(The old answer from Dec 27, 2010) (2010 年 12 月 27 日的旧答案)

Important changes made to KeithB's code : the buffer has to be either allocated by malloc or specified as NULL.KeithB 的代码所做的重要更改:缓冲区必须由 malloc 分配或指定为 NULL。 Do NOT allocate it on the stack.不要在堆栈上分配它。

It's wise to check that status as well.检查该状态也是明智的。

I failed to find HAVE_CXA_DEMANGLE .我找不到HAVE_CXA_DEMANGLE I check __GNUG__ although that does not guarantee that the code will even compile.我检查了__GNUG__虽然这并不能保证代码甚至可以编译。 Anyone has a better idea?有人有更好的主意吗?

#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 contains a demangler. Boost 核心包含一个去解码器。 Checkout core/demangle.hpp :结帐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>
}

It's basically just a wrapper for abi::__cxa_demangle , as has been suggested previously.如前所述,它基本上只是abi::__cxa_demangle的包装器。

If all we want is the unmangled type name for the purpose of logging, we can actually do this without using std::type_info or even RTTI at all.如果我们想要的只是用于记录的未修改类型名称,我们实际上可以在不使用std::type_info甚至 RTTI 的情况下做到这一点。

A slightly portable solution that works for the big 3 main compiler front-ends ( , , and ) would be to use a function template and extract the type name from the function name.适用于三大主要编译器前端( )的稍微可移植的解决方案是使用函数template并从函数名称中提取类型名称。

gcc and clang both offer __PRETTY_FUNCTION__ which is the name of a current function or function template with all type-argument in the string. gccclang都提供__PRETTY_FUNCTION__ ,它是当前函数或函数模板的名称,字符串中包含所有类型参数。 Similarly MSVC has __FUNCSIG__ which is equivalent.同样,MSVC 具有__FUNCSIG__ ,这是等效的。 Each of these are formatted a little differently, for example, for a call of void foo<int> , the compilers will output something different:其中每一个的格式都略有不同,例如,对于void foo<int>的调用,编译器将输出不同的内容:

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

Knowing this, it's just a matter of parsing out a prefix and suffix and wrapping this into a function in order to extract out the type name.知道了这一点,只需解析出前缀和后缀并将其包装到函数中以提取类型名称即可。

We can even use 's std::string_view and extended constexpr to get string names at compile-time , just by parsing the name of a template function.我们甚至可以使用std::string_view和扩展的constexpr编译时获取字符串名称,只需解析模板函数的名称即可。 This could also be done in any earlier C++ version, but this will still require some form of string parsing.这也可以在任何早期的 C++ 版本中完成,但这仍然需要某种形式的字符串解析。

For example:例如:

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

With this, you can call get_type_name<T>() to get a std::string_view at compile-time indicating the unmangled type name.有了这个,您可以调用get_type_name<T>()在编译时获取std::string_view指示未损坏的类型名称。

For example:例如:

std::cout << get_type_name<std::string>() << std::endl;

on GCC will output:在 GCC 上将输出:

std::__cxx11::basic_string<char>

and on clang will output:在铿锵声将输出:

std::basic_string<char>

Live Example现场示例


A similar augmentation to this approach which avoids a prefix and suffix is to assume that the function name is the same for all types, and search for a sentinel type to parse out the offset to the sentinel from each end.这种避免prefixsuffix的方法的类似增强是假设函数名称对于所有类型都是相同的,并搜索哨兵类型以解析出从每一端到哨兵的偏移量。 This ensures that the string searching only happens once, and the offset is assumed to find the string name each time.这确保了字符串搜索只发生一次,并且假定偏移量每次都找到字符串名称。 For example, using double as a simple sentinel:例如,使用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);
}

Live Example现场示例


This isn't portable to all compilers, but can be modified for any compiler that offers a __FUNCSIG__ / __PRETTY_FUNCTION__ equivalent;这不能移植到所有编译器,但可以针对任何提供__FUNCSIG__ / __PRETTY_FUNCTION__等效的编译器进行修改; it just requires a bit of parsing.它只需要一点解析。

note: This hasn't been fully tested, so there may be some bugs;注意:这还没有经过全面测试,因此可能存在一些错误; but the primary idea is to parse any output that contains the name in totality -- which is often a side-effect of __func__ -like outputs on compilers.但主要想法是解析任何包含完整名称的输出——这通常是编译器上__func__的输出的副作用。

This is what we use.这就是我们使用的。 HAVE_CXA_DEMANGLE is only set if available (recent versions of GCC only). 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  

Here, take a look at type_strings.hpp it contains a function that does what you want.在这里,看看type_strings.hpp它包含一个函数,可以满足你的需求。

If you just look for a demangling tool, which you eg could use to mangle stuff shown in a log file, take a look at c++filt , which comes with binutils.如果您只是在寻找一个去处理工具,例如可以用来处理日志文件中显示的内容,请查看 binutils 附带的c++filt It can demangle C++ and Java symbol names.它可以分解 C++ 和 Java 符号名称。

It's implementation defined, so it's not something that's going to be portable.它是实现定义的,所以它不是可移植的。 In MSVC++, name() is the undecorated name, and you have to look at raw_name() to get the decorated one.在 MSVC++ 中,name() 是未修饰的名称,您必须查看 raw_name() 才能获得修饰的名称。
Just a stab in the dark here, but under gcc, you might want to look at demangle.h在这里只是在黑暗中刺伤,但在 gcc 下,您可能想查看demangle.h

I also found a macro called __PRETTY_FUNCTION__ , which does the trick.我还发现了一个名为__PRETTY_FUNCTION__的宏,它可以解决问题。 It gives a pretty function name (figures :)).它给出了一个漂亮的函数名称(数字:))。 This is what I needed.这就是我需要的。

Ie it gives me the following:即它给了我以下信息:

virtual bool mutex::do_unlock()

But I don't think it works on other compilers.但我认为它不适用于其他编译器。

The accepted solution [1] works mostly well.公认的解决方案[1] 效果很好。 I found at least one case (and I wouldn't call it a corner case) where it does not report what I expected... with references.我发现至少一个案例(我不会称其为极端案例)它没有报告我的预期......带有参考。

For those cases, I found another solution, posted at the bottom.对于这些情况,我找到了另一种解决方案,贴在底部。

Problematic case (using type as defined in [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;

produces生产

Type of i is int
Type of ri is int

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

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

produces生产

Type of i is int
Type of ri is int&

as desired (at least by me)根据需要(至少由我)

Code .代码 It has to be in an included header, not in a separately compiled source, due to specialization issues.由于专业化问题,它必须在包含的标头中,而不是在单独编译的源中。 See undefined reference to template function for instance.例如,请参阅对模板函数的未定义引用

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

Not a complete solution, but you may want to look at what some of the standard (or widely supported) macro's define.不是一个完整的解决方案,但您可能想查看一些标准(或广泛支持的)宏的定义。 It's common in logging code to see the use of the macros:在记录代码中看到宏的使用是很常见的:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);

A slight variation on Ali's solution.阿里的解决方案略有不同。 If you want the code to still be very similar to如果您希望代码仍然非常相似

typeid(bla).name() , typeid(bla).name() ,

writing this instead改为写这个

Typeid(bla).name() (differing only in capital first letter) Typeid(bla).name() (仅首字母大写不同)

then you may be interested in this:那么您可能对此感兴趣:

In file type.hpp在文件类型.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 stays same as in Ali's solution 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

Following Ali's solution, here is the C++11 templated alternative which worked best for my usage.遵循 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();
}

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

Will print:将打印:

double                                                                        
int                                                                           
test::SomeStruct

I've always wanted to use type_info, but I'm sure that the result of the name() member function is non-standard and won't necessarily return anything that can be converted to a meaningful result.我一直想使用 type_info,但我确信 name() 成员函数的结果是非标准的,不一定会返回任何可以转换为有意义的结果的东西。
If you are sticking to one compiler, there maybe a compiler specific function that will do what you want.如果您坚持使用一个编译器,那么可能会有一个特定于编译器的函数可以满足您的需求。 Check the documentation.检查文档。

boost::typeindex provides something helpful. 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;
}

The output is输出是

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

What is worthy of notice is that type_id_with_cvr preserves reference and c/v qualifiers, while typeid doesn't.值得注意的是type_id_with_cvr保留了引用和 c/v 限定符,而typeid没有。 See the following example:请参见以下示例:

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

The output is输出是

PK6Widget
Widget const* const&

Here, typeid produces PK6Widget , which means P ointer to K onst Widget.在这里, typeid产生PK6Widget ,这意味着指向K onst Widget 的指针。 The number '6' is the length of the name 'Widget'.数字“6”是名称“Widget”的长度。 This is not the correct type of param , in which the reference and const qualifier are dropped.这不是param的正确类型,其中删除了引用和 const 限定符。

The type_id_with_cvr actually uses the demangling functions in boost::core , as has been mentioned in this answer . type_id_with_cvr实际上使用了boost::core中的解组函数,正如这个答案中所提到的。 To preserve the cv qualifiers or reference, it just defines an empty template named cvr_saver and then passes cvr_saver<type> to typeid .为了保留 cv 限定符或引用,它只定义了一个名为cvr_saver的空模板,然后将cvr_saver<type>传递给typeid

Effective Modern C++ Item 4 talks about this. Effective Modern C++ Item 4 谈到了这一点。

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

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