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

我目前正在编写一些日志代码,其中包括打印有关调用函数的信息。这应该相对容易,标准 C + + 有一个 type_info类。它包含 typeid’d 类/函数/等的名称,但是它被破坏了。没什么用。即 typeid(std::vector<int>).name()返回 St6vectorIiSaIiEE

有没有什么方法能从这个中得到有用的东西?对于上面的例子,类似于 std::vector<int>。如果它只适用于非模板类,那也没问题。

这个解决方案应该适用于 gcc,但是如果我能够移植它会更好。它是用于日志记录的,所以不会因为太重要而无法关闭,但它应该对调试有帮助。

54156 次浏览

它是实现定义的,所以它不是可移植的。在 MSVC + + 中,name ()是未修饰的名称,必须查看 raw _ name ()才能得到修饰的名称。
这里只是瞎猜一下,但是在 gcc 下,你可能想看看 demangle.h

我一直想使用 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.

看看 __cxa_demangle,你可以在 cxxabi.h找到它。

这就是我们使用的方法。只有在可用的情况下(仅限于 GCC 的最新版本)才设置 HAVE _ CXA _ DEMANGLE。

#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

这不是一个完整的解决方案,但是您可能希望了解一些标准(或广泛支持的)宏的定义。在日志记录代码中经常可以看到宏的使用:

__FUNCTION__
__FILE__
__LINE__


e.g.:


log(__FILE__, __LINE__, __FUNCTION__, mymessage);

这里,看一下 Type _ string. hpp,它包含一个函数,可以完成您想要的任务。

如果你只是在寻找一个分解工具,例如,你可以用它来分解日志文件中显示的东西,看看 c++filt,它带有 binutils。它可以分离 C + + 和 Java 符号名。

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

也就是说,它给了我以下信息:

virtual bool mutex::do_unlock()

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

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

考虑到这个问题/答案受到的关注,以及来自 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*
指针类型: Derived

在 Linux 64位和 g + + 4.7.2(Mingw32,Win32 XP SP2)上用 g + + 4.9.020140302(实验) ,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()的调用成功时,returns a pointer to a local, stack allocated array... 哎哟!
还要注意,如果您提供了一个缓冲区,则 abi::__cxa_demangle()假设它是在堆上分配的。在堆栈上分配缓冲区是一个 bug (来自 gnu doc) : 如果 ABC1不够长,就用 realloc展开 在指向堆栈的指针上调用 realloc()... 哎哟!(请参阅 Igor Skochinsky的友好评论。)

您可以很容易地验证这两个 bug: 只需将接受答案中的缓冲区大小(截至2013年9月7日)从1024减小到更小的值,例如16,并给它一个名称为 没有长于15的值(因此 realloc()被称为 没有)。然而,根据您的系统和编译器的优化,输出结果将是: 垃圾/无/程序崩溃。
To verify the second bug: set the buffer size to 1 and call it with something whose name is longer than 1 character. When you run it, the program almost assuredly crashes as it attempts to call realloc() with a pointer to the stack.


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

KeithB 的密码所做的重要更改: the buffer has to be either allocated by malloc or specified as 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;
}

A slight variation on Ali's solution. If you want the code to still be very similar to

typeid(bla).name(),

而是写这个

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

then you may be interested in this:

In file 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 的解决方案相同

Boost core contains a 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>
}

它基本上只是 abi::__cxa_demangle的一个包装器,如前所述。

accepted solution[1]基本上运行良好。 我发现至少有一个案例(我不会称之为角落案例) ,它没有报告我所期望的... 与参考。

For those cases, I found another solution, posted at the bottom.

问题案例 (使用[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;
}

按照阿里的解决方案,这里是 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;
}

Will print:

double
int
test::SomeStruct

如果我们所需要的只是用于日志记录的未损坏的类型名称,那么实际上我们不需要使用 std::type_info甚至 RTTI 就可以做到这一点。

一个适用于3个主要编译器前端()的稍微可移植的解决方案是使用函数 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. Similarly MSVC has __FUNCSIG__ which is equivalent. Each of these are formatted a little differently, for example, for a call of void foo<int>, the compilers will output something different:

  • gcc is formatted void foo() [with T = int; ]
  • clang is formatted 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

For example:

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

海湾合作委员会将输出:

std::__cxx11::basic_string<char>

而当当声将输出:

std::basic_string<char>

Live Example


对这种避免使用 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 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);
}

Live Example


这不能移植到 所有编译器,但是可以为任何提供 __FUNCSIG__/__PRETTY_FUNCTION__等价物的编译器进行修改; 它只需要一点解析。

注意: 这还没有经过 完全测试,所以可能存在一些 bug; 但是主要的想法是解析任何包含全名的输出——这通常是类似 __func__的输出对编译器的副作用。

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

The output is

PK6Widget
Widget const* const&

在这里,typeid产生 PK6Widget,这意味着 Pointer 到 KonstWidget。数字“6”是名称“ Widget”的长度。这不是 param的正确类型,其中删除了引用和常量限定符。

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

有效的现代 C + + 第4项谈到了这一点。