Как записать собственный dynamic_cast

Это спросили в интервью.

Как записать собственный dynamic_cast. Я думаю, на основе функции имени идентификатора типа.

Теперь, как реализовать собственный typid? У меня нет подсказки о нем.

5
задан Darin Dimitrov 4 July 2010 в 08:02
поделиться

4 ответа

Есть причина, по которой вы не знаете, dynamic_cast и static_cast не похожи на const_cast или ] reinterpret_cast , они фактически выполняют арифметические операции с указателями и в некоторой степени безопасны для типов.

Арифметика указателя

Чтобы проиллюстрировать это, представьте себе следующую схему:

struct Base1 { virtual ~Base1(); char a; };
struct Base2 { virtual ~Base2(); char b; };

struct Derived: Base1, Base2 {};

Экземпляр Derived должен выглядеть примерно так (он основан на gcc, поскольку фактически зависит от компилятора ...):

|      Cell 1       | Cell 2 |      Cell 3        | Cell 4 |
| vtable pointer    |    a   | vtable pointer     |    b   |
|         Base 1             |        Base 2               |
|                     Derived                              |

Теперь подумайте о работе, необходимой для литья:

  • преобразование из Derived в Base1 не требует дополнительной работы, они находятся на том же физическом уровне преобразование адреса
  • из Производный в Base2 требует сдвига указателя на 2 байта

Следовательно, необходимо знать структуру памяти объектов, чтобы иметь возможность преобразовать между одним производным объектом и одним из его базовых. И это известно только компилятору, информация недоступна через какой-либо API, она не стандартизирована или что-то еще.

В коде это будет выглядеть так:

Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);

И это, конечно, для static_cast .

Теперь, если вы могли использовать static_cast в реализации dynamic_cast , то вы могли бы усилить компилятор и позволить ему обрабатывать арифметические операции с указателем за вас ... но вы все еще не из дерева.

Написание dynamic_cast?

Прежде всего, нам нужно уточнить спецификации dynamic_cast :

  • dynamic_cast (& base); возвращает null, если base не является экземпляром Derived .
  • dynamic_cast (base); в этом случае выбрасывает std :: bad_cast .
  • dynamic_cast (base); возвращает адрес самого производного класса
  • dynamic_cast с учетом спецификаций доступа ( public , protected и частное наследование)

Не знаю как вы, но я думаю, что это будет некрасиво. Использование typeid здесь недостаточно:

struct Base { virtual ~Base(); };
struct Intermediate: Base {};
struct Derived: Base {};

void func()
{
  Derived derived;
  Base& base = derived;
  Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg
}

Проблема в том, что typeid (base) == typeid (Derived)! = Typeid (Intermediate) , поэтому вы не можете полагаться на это тоже.

Еще одна забавная вещь:

struct Base { virtual ~Base(); };
struct Derived: virtual Base {};

void func(Base& base)
{
  Derived& derived = static_cast<Derived&>(base); // Fails
}

static_cast не работает, когда задействовано виртуальное наследование ... так что мы пошли на ползучую проблему арифметических вычислений указателей.

Почти решение

class Object
{
public:
  Object(): mMostDerived(0) {}
  virtual ~Object() {}

  void* GetMostDerived() const { return mMostDerived; }

  template <class T>
  T* dynamiccast()
  {
    Object const& me = *this;
    return const_cast<T*>(me.dynamiccast());
  }

  template <class T>
  T const* dynamiccast() const
  {
    char const* name = typeid(T).name();
    derived_t::const_iterator it = mDeriveds.find(name);
    if (it == mDeriveds.end()) { return 0; }
    else { return reinterpret_cast<T const*>(it->second); }
  }

protected:
  template <class T>
  void add(T* t)
  {
    void* address = t;
    mDerived[typeid(t).name()] = address;
    if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; }
  }

private:
  typedef std::map < char const*, void* > derived_t;
  void* mMostDerived;
  derived_t mDeriveds;
};

// Purposely no doing anything to help swapping...

template <class T>
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T& dynamiccast(Object& o)
{
  if (T* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

template <class T>
T const& dynamiccast(Object const& o)
{
  if (T const* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

Вы нужны мелочи в конструкторе:

class Base: public Object
{
public:
  Base() { this->add(this); }
};

Итак, проверим:

  • классическое использование: хорошо
  • виртуальное наследование? он должен работать ... но не тестировался
  • на соответствие спецификаторам доступа ...ARG: /

Удачи всем, кто пытается реализовать это вне компилятора, правда: x

20
ответ дан 18 December 2019 в 07:53
поделиться

Чтобы попытаться ответить на этот вопрос чуть менее рутинно, если чуть менее определенно:

Что вам нужно сделать, так это привести указатель к int*, создать новый тип T на стеке, привести указатель на него к int*, и сравнить первый int в обоих типах. При этом будет выполнено сравнение адресов в таблице. Если они одного типа, то у них будет одна и та же таблица. В противном случае - нет.

Более разумные из нас просто вставляют интегральную константу в наши классы.

0
ответ дан 18 December 2019 в 07:53
поделиться

Легко. Получите все объекты из некоторого интерфейса typeid с помощью виртуальной функции WhoAmI (). Переопределите его во всех производных классах.

-1
ответ дан 18 December 2019 в 07:53
поделиться

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

Статический идентификатор должен быть инициализирован при инициализации приложения. Один из способов - вызвать подпрограмму InitializeId для каждого класса, но это означает, что имена классов должны быть известны, а код инициализации должен изменяться каждый раз, когда изменяется иерархия классов. Другой способ - проверить действительный идентификатор во время создания, но это приводит к накладным расходам, поскольку каждый раз, когда создается класс, проверка выполняется, но полезна только в первый раз (кроме того, если класс не построен, статическая процедура не может быть полезной. поскольку идентификатор никогда не инициализируется).

Хорошей реализацией может быть шаблонный класс:

template <typename T>
class ClassId<T>
{
    public:

    static int GetClassId() { return (sClassId); }

    virtual int GetClassId() const { return (sClassId); }

    template<typename U> static void StateDerivation() {
        gClassMap[ClassId<T>::GetClassId()].push_back(ClassId<U>::GetClassId());
    }

    template<typename U> const U DynamicCast() const {
        std::map<int, std::list<int>>::const_iterator it = gClassMap.find(ClassId<T>); // Base class type, with relative derivations declared with StateDerivation()
        int id = ClassId<U>::GetClassId();

        if (id == ClassId<T>::GetClassId()) return (static_cast<U>(this));

        while (it != gClassMap.end()) {
            for (std::list<int>::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) {
                if ((*pit) == id) return (static_cast<U>(this));
                // ... For each derived element, iterate over the stated derivations.
                // Easy to implement with a recursive function, better if using a std::stack to avoid recursion.
            }
        }

        return (null); 
    }  

    private:

    static int sClassId;
}

#define CLASS_IMP(klass) static int ClassId<klass>::sClassId = gClassId++;

// Global scope variables    

static int gClassId = 0;
static std::map<int, std::list<int>> gClassMap;

CLASS_IMP должен быть определен для каждого класса, производного от ClassId, а gClassId и gClassMap должны быть видимы в глобальной области.

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

Для представления иерархии классов достаточно сопоставления идентификатора класса и его производных классов. Чтобы узнать, можно ли отнести какой-либо класс к определенному классу, выполните итерацию по карте и проверьте производные объявления.

Есть много трудностей, с которыми приходится сталкиваться ... использование ссылок! виртуальные производные! плохой кастинг! Неправильная инициализация сопоставления типов классов приведет к ошибкам приведения типов ...


Отношения между классами должны определяться вручную, жестко запрограммированные с помощью процедуры инициализации. Это позволяет определить, является ли класс производным от или два класса как общие производные.

class Base : ClassId<Base> {  }
#define CLASS_IMP(Base);
class Derived : public Base, public ClassId<Derived> {  }
#define CLASS_IMP(Derived);
class DerivedDerived : public Derived, public ClassId<DerivedDerived> {  }
#define CLASS_IMP(DerivedDerived);

static void DeclareDerivations()
{
    ClassId<Base>::StateDerivation<Derived>();
    ClassId<Derived>::StateDerivation<DerivedDerived>();
}

Лично я согласен с тем, что «есть причина, если компиляторы реализуют dynamic_cast»; возможно, компилятор делает это лучше (особенно в отношении кода примера!).

1
ответ дан 18 December 2019 в 07:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: