Отмена результата std::type_info::name
В настоящее время я работаю над кодом регистрации, который должен - среди прочего - печатать информацию о вызывающей функции. Это должно быть относительно легко, стандарт C++ имеет type_info
учебный класс. Он содержит имя класса / функции typeid'd и т. Д. но он покалечен Это не очень полезно. Т.е. typeid(std::vector<int>).name()
возвращается St6vectorIiSaIiEE
,
Есть ли способ получить что-то полезное из этого? подобно std::vector<int>
для приведенного выше примера. Если это работает только для не шаблонных классов, это тоже хорошо.
Решение должно работать для gcc, но было бы лучше, если бы я мог его портировать. Это для регистрации, поэтому не так важно, чтобы его нельзя было отключить, но это должно быть полезно для отладки.
16 ответов
Учитывая внимание, которое получает этот вопрос / ответ, и ценный отзыв от 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
В файле типа .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
Протестировано с g++ 4.7.2, g++ 4.9.0 20140302 (экспериментальный), clang++ 3.4 (транк 184647), clang 3.5 (транк 202594) в 64-битной Linux и 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
(Обновление от 8 сентября 2013 г.)
Принятый ответ (по состоянию на 7 сентября 2013 г.), когда abi::__cxa_demangle()
успешно, возвращает указатель на локальный массив, выделенный стеком... ой!
Также обратите внимание, что если вы предоставите буфер, abi::__cxa_demangle()
Предполагается, что он будет размещен в куче. Выделение буфера в стеке - ошибка (из документа gnu): "Если output_buffer
недостаточно длинный, он расширяется с помощью realloc
" Зовет realloc()
на указатель на стек... ой! (См. Также добрый комментарий Игоря Скочинского.)
Вы можете легко проверить обе эти ошибки: просто уменьшите размер буфера в принятом ответе (по состоянию на 7 сентября 2013 г.) с 1024 до чего-то меньшего, например, 16, и дайте ему что-то с именем не длиннее 15 (так realloc()
не называется). Тем не менее, в зависимости от вашей системы и оптимизации компилятора, вывод будет: мусор / ничего / сбой программы.
Чтобы проверить вторую ошибку: установите размер буфера равным 1 и вызовите его с именем, длина которого превышает 1 символ. Когда вы запускаете его, программа почти наверняка вылетает при попытке вызвать realloc()
с указателем на стек.
(Старый ответ от 27 декабря 2010 г.)
Важные изменения, внесенные в код 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
, как было предложено ранее.
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.
A slightly portable solution that works for the big 3 main compiler front-ends (gcc, clang, and msvc) would be to use a function
template
and extract the type name from the function name.
and both offer which is the name of a current function or function template with all type-argument in the string. Similarly MSVC has 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 formattedvoid foo() [with T = int; ]
-
clang
is formattedvoid foo() [T = int]
-
msvc
is formattedvoid 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 c++17's and extended
constexpr
to get string names at compile-time, just by parsing the name of a template function. This could also be done in any earlier C++ version, but this will still require some form of string parsing.
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.
For example:
std::cout << get_type_name<std::string>() << std::endl;
on GCC will output:
std::__cxx11::basic_string<char>
and on clang will output:
std::basic_string<char>
This isn't portable to all compilers, but can be modified for any compiler that offers a
__FUNCSIG__
/
__PRETTY_FUNCTION__
equivalent; 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.
Это то, что мы используем. 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, он содержит функцию, которая делает то, что вы хотите.
Если вы просто ищете инструмент разборки, который вы, например, можете использовать для манипулирования вещами, показанными в файле журнала, взгляните на c++filt
, который поставляется с binutils. Он может разделять имена символов C++ и Java.
Не полное решение, но вы можете посмотреть, что определяют некоторые стандартные (или широко поддерживаемые) макросы. В журнале кода часто можно увидеть использование макросов:
__FUNCTION__
__FILE__
__LINE__
e.g.:
log(__FILE__, __LINE__, __FUNCTION__, mymessage);
Это определяется реализацией, поэтому она не будет переносимой. В MSVC++ name() - это неокрашенное имя, и вы должны взглянуть на raw_name(), чтобы получить декорированное.
Просто удар в темноте, но в gcc вы можете посмотреть на demangle.h
Я также нашел макрос под названием __PRETTY_FUNCTION__
, который делает свое дело. Это дает красивое имя функции (цифры:)). Это то, что мне было нужно.
Т.е. это дает мне следующее:
virtual bool mutex::do_unlock()
Но я не думаю, что это работает на других компиляторах.
Принятое решение [1] работает в основном хорошо. Я нашел по крайней мере один случай (и я бы не назвал это угловым случаем), где он не сообщает, что я ожидал... со ссылками.
Для этих случаев я нашел другое решение, размещенное внизу.
Проблемный случай (с использованием type
как определено в [1]):
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;
}
Небольшое отклонение от решения Али. Если вы хотите, чтобы код все еще был очень похож на
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 остается таким же, как в решении Али
Следуя решению Али, вот шаблонная альтернатива 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
// 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
,
Я всегда хотел использовать type_info, но я уверен, что результат функции-члена name() нестандартен и не обязательно будет возвращать что-либо, что может быть преобразовано в значимый результат.
Если вы придерживаетесь одного компилятора, возможно, есть специальная функция компилятора, которая будет делать то, что вы хотите. Проверьте документацию.
Если вам нужно только прочитать выходные данные вашей программы, которые печатаютtypeid(XXX).name()
для целей отладки вы можете использовать
c++filt -t
в командной строке, чтобы легко разобрать вывод:
$ ./program | c++filt -t
Например:
$ echo 'St6vectorIiSaIiEE' | c++filt -t
std::vector<int, std::allocator<int>>
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&
Что заслуживает внимания, так это сохранение ссылок и квалификаторов c/v, а не сохранение. См. следующий пример:
#include <iostream>
#include <boost/type_index.hpp>
#include <typeindex>
#include <vector>
#include <typeinfo>
class Widget {};
template <typename T>
void f(const T ¶m) {
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&
Здесь производят
PK6Widget
, что означает указатель на K onst Widget. Число «6» — это длина имени «Виджет». Это не правильный тип
param
, в котором ссылка и квалификатор const удалены.
В
type_id_with_cvr
на самом деле использует функции разборки в
boost::core
, как было упомянуто в этом ответе . Чтобы сохранить квалификаторы или ссылку cv, он просто определяет пустой шаблон с именем
cvr_saver
а потом проходит
cvr_saver<type>
к
typeid
.
Эффективный современный C++, пункт 4, говорит об этом.