Использование RTTI для определения графа наследования в C ++? - PullRequest
18 голосов
/ 09 августа 2011

Что, если таковые имеются, существуют конструкции c ++ для перечисления предков класса во время выполнения?

По сути, у меня есть класс, в котором хранится указатель на любой объект, включая, возможно, примитивный тип (что-то вроде 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» и т. д.? Если у меня есть это, есть ли безопасный способ на самом деле выполнить этот акт?

Ответы [ 5 ]

11 голосов
/ 08 января 2012

У меня была похожая проблема, которую я решил с помощью исключений!Я написал статью об этом:

http://drdobbs.com/cpp/229401004

Хорошо.Следуя совету Петра, следует набросок идеи.Он основан на том факте, что если D происходит от B и выбрасывается указатель на D, то будет активировано предложение catch, ожидающее указатель на B.

Затем можно написатькласс (в моей статье я назвал его any_ptr), конструктор шаблона которого принимает T* и сохраняет его копию как void*.Класс реализует механизм, который статически приводит void* к исходному типу T* и выдает результат.Предложение catch, ожидающее U*, где U = T или U является основанием T, будет активировано, и эта стратегия является ключом к реализации теста, как в первоначальном вопросе.

РЕДАКТИРОВАТЬ: (по Matthieu M. для ответов являются наиболее самостоятельными, пожалуйста, обратитесь к доктору Доббс для полного ответа)

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;
    }
};
5 голосов
/ 26 июля 2012

Информация (часто) присутствует в реализации. Там нет никакого стандартного C ++ способа получить к нему доступ, но он не раскрыт. Если вы хотите привязать себя к конкретным реализациям или наборам реализаций, вы можете сыграть в грязную игру, чтобы найти информацию до сих пор.

Пример использования 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 к реальному типу, который используется внутри, а затем рекурсивно использовал его для обхода дерева наследования.

Я бы не рекомендовал делать это в реальном коде, но в качестве упражнения в деталях реализации это не невозможно.

4 голосов
/ 09 августа 2011

Во-первых, то, что вы просите, не может быть реализовано просто поверх type_info.

В C ++, чтобы приведение происходило от одного объекта к другому, вам нужно большечем вслепую при условии, что тип может использоваться как другой, вам также необходимо настроить указатель из-за множественного наследования (смещение во время компиляции) и виртуального наследования (смещение во время выполнения).

только способ безопасно преобразовать значение из типа в другой, это использовать static_cast (работает для одиночного или множественного наследования) и dynamic_cast (также работает для виртуального наследования и фактически проверяет среду выполнениязначения).

К сожалению, это на самом деле несовместимо со стиранием типов (старая несовместимость template-virtual).

Если вы ограничите самим не-виртуальным наследованием, яЯ думаю, что этого можно достичь, сохраняя смещения преобразований в различные базы в некоторых Configuration данных (синглетах, о которых вы говорите).

Для виртуального наследования я могу думать толькокарты пар от type_info до void* (*caster)(void*).

И все это требует перечисления возможных приведений вручную: (

1 голос
/ 09 августа 2011

Невозможно использовать std::type_info, поскольку он не обеспечивает способ запроса информации о наследовании или преобразования объекта std::type_info в соответствующий ему тип, чтобы вы могли выполнить приведение.

Если выу вас есть список всех возможных типов, которые вам нужно сохранить в ваших any объектах, которые используют boost::variant и его посетителя.

0 голосов
/ 09 августа 2011

Хотя я не могу придумать какой-либо способ реализации опции № 1, вариант № 2 должен быть осуществимым, если вы можете сгенерировать список классов компиляции, которые вы хотели бы использовать во время компиляции. Отфильтруйте этот список типов с помощью boost :: MPL и метафункции is_base_of, чтобы получить список допустимых типов приведенных типов, которые можно сравнить с сохраненным типом.

...