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

新手上路,请多包涵

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

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

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

原文由 terminus 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

鉴于这个问题/答案受到的关注,以及来自 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;
}

原文由 Ali 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

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

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

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

知道了这一点,只需解析出前缀和后缀并将其包装到函数中以提取类型名称即可。

我们甚至可以使用 c++17std::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 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__ 的副作用 - 类似编译器的输出。

原文由 Human-Compiler 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题