Использование RTTI для определения графика наследования в С++?
Что, если есть, конструкции С++ существуют для перечисления предков класса во время выполнения?
В принципе, у меня есть класс, который хранит указатель на любой объект, включая, возможно, примитивный тип (несколько похожий на boost::any
, который я не хочу использовать, потому что мне нужно сохранить право собственности на мои объекты). Внутри этот указатель имеет значение void*
, но цель этого класса состоит в том, чтобы обернуть void*
безопасностью типа времени выполнения. Оператор присваивания шаблонизирован, поэтому во время назначения я беру typeid()
входящего указателя и сохраняю его. Затем, когда я вернусь позже, я могу проверить typeid()
типа броска на сохраненный type_info
. Если он не соответствует действительности, бросок будет генерировать исключение.
Но есть проблема: Кажется, я теряю полиморфизм. Пусть говорят, что B
является базисом D
. Если я сохраняю указатель на D
в моем классе, то сохраненный type_info
также будет иметь значение D
. Затем, возможно, я захочу получить указатель B
. Если я использую метод класса для преобразования в B*
, то typeid(B) == typeid(D)
завершается с ошибкой, а листинг вызывает исключение, хотя преобразование D->B
безопасно. Dynamic_cast<>()
здесь не применяется, так как я работаю с void*
, а не с предком B
или D
.
То, что я хотел бы сделать, это проверить is_ancestor(typeid(B), typeid(D))
. Возможно ли это? (И не это то, что dynamic_cast<>
делает за кулисами?)
Если нет, тогда я думаю о том, чтобы использовать второй подход: реализовать класс TypeInfo
, производными классами которого являются шаблонные синглтоны. Затем я могу сохранить любую информацию, которая мне нравится в этих классах, а затем сохранить указатели на них в классе AnyPointer
. Это позволило бы мне генерировать/хранить информацию о предках во время компиляции более доступным способом. Таким образом, неудачный вариант №1 (встроенный способ перечисления предков, которым предоставляется только информация, доступная во время выполнения), есть ли конструкция/процедура, которую я могу использовать, которая позволит генерировать и сохранять информацию о предках автоматически во время компиляции, предпочтительно без чтобы явно ввести, что "класс A
происходит от B
и C
; C
происходит от D
" и т.д.? Как только у меня есть это, есть ли безопасный способ выполнить этот бросок?
Ответы
Ответ 1
У меня была аналогичная проблема, которую я решил через исключения! Я написал статью об этом:
http://drdobbs.com/cpp/229401004
Ok. Следуя совету Питера, набросок идеи следует. Он полагается на то, что если D
происходит от B
и вызывается указатель на D
, тогда будет активировано предложение catch, ожидающее указателя на B
.
Затем можно написать класс (в моей статье я назвал его any_ptr
), конструктор которого принимает T*
и сохраняет его копию как void*
. Класс реализует механизм, который статически ставит void*
в его исходный тип T*
и выдает результат. Предложение catch, ожидающее U*
, где U
= T
или U
является базой T
, будет активировано, и эта стратегия является ключом к реализации теста, как в исходном вопросе.
РЕДАКТИРОВАТЬ: (по Маттиу М. для ответов лучше всего автономно, пожалуйста, обратитесь к доктору Добсу за полный ответ)
class any_ptr {
void* ptr_;
void (*thr_)(void*);
template <typename T>
static void thrower(void* ptr) { throw static_cast<T*>(ptr); }
public:
template <typename T>
any_ptr(T* ptr) : ptr_(ptr), thr_(&thrower<T>) {}
template <typename U>
U* cast() const {
try { thr_(ptr_); }
catch (U* ptr) { return ptr; }
catch (...) {}
return 0;
}
};
Ответ 2
Информация (часто) существует в рамках реализации. Там нет стандартного С++-способа доступа к нему, хотя он не отображается. Если вы хотите привязать себя к конкретным реализациям или наборам реализаций, вы можете сыграть в грязную игру, чтобы найти информацию.
Пример для gcc с использованием Itanium ABI:
#include <cassert>
#include <typeinfo>
#include <cxxabi.h>
#include <iostream>
bool is_ancestor(const std::type_info& a, const std::type_info& b);
namespace {
bool walk_tree(const __cxxabiv1::__si_class_type_info *si, const std::type_info& a) {
return si->__base_type == &a ? true : is_ancestor(a, *si->__base_type);
}
bool walk_tree(const __cxxabiv1::__vmi_class_type_info *mi, const std::type_info& a) {
for (unsigned int i = 0; i < mi->__base_count; ++i) {
if (is_ancestor(a, *mi->__base_info[i].__base_type))
return true;
}
return false;
}
}
bool is_ancestor(const std::type_info& a, const std::type_info& b) {
if (a==b)
return true;
const __cxxabiv1::__si_class_type_info *si = dynamic_cast<const __cxxabiv1::__si_class_type_info*>(&b);
if (si)
return walk_tree(si, a);
const __cxxabiv1::__vmi_class_type_info *mi = dynamic_cast<const __cxxabiv1::__vmi_class_type_info*>(&b);
if (mi)
return walk_tree(mi, a);
return false;
}
struct foo {};
struct bar : foo {};
struct baz {};
struct crazy : virtual foo, virtual bar, virtual baz {};
int main() {
std::cout << is_ancestor(typeid(foo), typeid(bar)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(baz)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(int)) << "\n";
std::cout << is_ancestor(typeid(foo), typeid(crazy)) << "\n";
}
Где я применяю type_info
к реальному типу, который используется внутренне, а затем рекурсивно используется для перемещения дерева наследования.
Я бы не рекомендовал делать это в реальном коде, но как упражнение в деталях реализации это не невозможно.
Ответ 3
Во-первых, то, что вы просите, не может быть реализовано только поверх type_info
.
В С++ для трансляции из одного объекта в другой вам нужно больше, чем слепо предположить, что тип может использоваться как другой, вам также нужно настроить указатель из-за многонаследования (смещения времени компиляции) и виртуальное наследование (смещение во время выполнения).
Единственный способ безопасно отличить значение от типа к другому - это использовать static_cast
(работает для одиночного или множественного наследования) и dynamic_cast
(также работает для виртуального наследования и фактически проверяет значения времени выполнения).
К сожалению, это фактически несовместимо с стиранием типа (старая template-virtual
несовместимость).
Если вы ограничиваете себя не виртуальным наследованием, я думаю, что это должно быть возможным для этого, сохраняя смещения преобразований на различные базы в некоторых Configuration
данных (о синглтонах, о которых вы говорите).
Для виртуального наследования я могу только думать о карте пар type_info
в void* (*caster)(void*)
.
И все это требует перечисления возможных бросков вручную: (
Ответ 4
Невозможно использовать std::type_info
, так как он не предоставляет способ запроса информации наследования или преобразования объекта std::type_info
в соответствующий тип, чтобы вы могли сделать трансляцию.
Если у вас есть список всех возможных типов, которые необходимо сохранить в ваших объектах any
, используйте boost::variant
и его посетителя.
Ответ 5
Хотя я не могу придумать какой-либо способ реализации опции №1, вариант №2 должен быть осуществимым, если вы можете сгенерировать список времени компиляции классов, которые вы хотели бы использовать. Отфильтруйте этот список типов с помощью boost:: MPL и метафайла is_base_of
, чтобы получить список допустимых типов типов, которые можно сравнить с сохраненным типом.