C++ - уменьшение унаследованного объекта в форме ромба без RTTI/dynamic_cast

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

Проблема в том, что у многих классов есть проблема наследования алмазов, поскольку все классы являются производными от одного и того же базового класса (объекта)... и поэтому, если я хочу перейти от базового класса к производному классу, я должен использовать a dynamic_cast - но RTTI недоступен! Как преобразовать объект из родительского в дочерний, если есть виртуальное наследование без dynamic_cast?

Это выглядит так:

class A 
{
public:
 virtual char* func() { return "A"; };
};
class B : public virtual A
{
public:
 //virtual char* func() { return "B"; };
};
class C : public virtual A 
{
public:
 //virtual char* func() { return "C"; };
};

class D : public B, public C 
{
public:
 //virtual char* func() { return "D"; };
};

D d;
A* pa = static_cast<A*>(&d);
D* pd = static_cast<D*>(pa); // can't do that! dynamic_cast does work though...

Это мои ошибки:

ошибка C2635: невозможно преобразовать "A*" в "D*"; подразумевается преобразование из виртуального базового класса

ошибка C2440: "инициализация": невозможно преобразовать из "test_convert::A *" в "test_convert::D *" Приведение из базы в производное требует dynamic_cast или static_cast

Есть идеи?

6 ответов

Вы можете сделать это только с dynamic_cast; никакой другой актерский состав не сделает этого.

Если вы не можете спроектировать свои интерфейсы так, чтобы вам не нужно было выполнять этот тип приведения, то единственное, что вы можете сделать, - это сделать функциональность приведения частью вашей иерархии классов.

Например (ужасно хаки)

class D;

class A
{
public:
    virtual D* GetDPtr() { return 0; }
};

class B : public virtual A
{
};

class C : public virtual A 
{
};

class D : public B, public C 
{
public:
    virtual D* GetDPtr() { return this; }
};

Android поддерживает RTTI. Вам нужен самый последний NDK (по крайней мере, r5, самый последний - r6), и вам нужно скомпилировать с GNU stdlibC++ вместо значения по умолчанию.

Еще раньше была перестройка CrystaX, которая поддерживала исключения и rtti (мы должны были использовать это до официального NDK r5c, потому что r5a и r5b имели поддержку, но выходили из строя на старых (до 2.3) системах).

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

В большинстве случаев шаблон посетителя может использоваться, чтобы избежать понижений. Это может также использоваться, чтобы избежать dynamic_cast.

Некоторые предостережения:

1) Должна быть возможность изменить нарушающие классы.
2) Возможно, вам нужно знать КАЖДЫЙ производный класс.
3) Должно быть известно, что объекты являются производными как минимум от базового класса, вы не можете пытаться приводить совершенно не связанные типы. (Это, кажется, выполнено: "Я хочу понизить класс с базового до производного класса")

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

class A;
class B;
class C;
class D;

// completely abstract Visitor-baseclass.
// each visit-method must return whether it handled the object
class Visitor
{ 
public:
    virtual bool visit(A&) = 0;
    virtual bool visit(B&) = 0;
    virtual bool visit(C&) = 0;
    virtual bool visit(D&) = 0;
};

class A
{
public:
    virtual const char* func() { return "A"; };
    virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};
class B : public virtual A
{
public:
    virtual const char* func() { return "B"; };
    virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};
class C : public virtual A
{
public:
    virtual const char* func() { return "C"; };
    virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};
class D : public B, public C
{
public:
    virtual const char* func() { return "D"; };
    virtual void accept(Visitor& visitor) { visitor.visit(*this); }
};

// implementation-superclass for visitors: 
// each visit-method is implemented and calls the visit-method with the parent-type(s)
class InheritanceVisitor : public Visitor
{ 
    virtual bool visit(A& a) { return false; }
    virtual bool visit(B& b) { return visit(static_cast<A&>(b)); }
    virtual bool visit(C& c) { return visit(static_cast<A&>(c)); }
    virtual bool visit(D& d) { return visit(static_cast<B&>(d)) || visit(static_cast<C&>(d)); }
};

template<typename T> // T must derive from A
class DerivedCastVisitor : public InheritanceVisitor
{
public:
    DerivedCastVisitor(T*& casted) : m_casted(casted) {}
    virtual bool visit(T& t) 
    { m_casted = &t; return true; }
private:
    T*& m_casted;
};

// If obj is derived from type T, then obj is casted to T* and returned. 
// Else NULL is returned.
template<typename T> 
T* derived_cast(A* obj)
{
  T* t = NULL;
  if (obj) 
  {
    DerivedCastVisitor<T> visitor(t);
    obj->accept(visitor);
  }
  return t;
}

int main(int argc, char** argv)
{
  std::auto_ptr<A> a(new A);
  std::auto_ptr<A> b(new B);
  std::auto_ptr<A> c(new C);
  std::auto_ptr<A> d(new D);

  assert(derived_cast<A>(a.get()) != NULL); // a has exact type A
  assert(derived_cast<B>(b.get()) != NULL); // b has exact type B
  assert(derived_cast<A>(b.get()) != NULL); // b is derived of A
  assert(derived_cast<C>(b.get()) == NULL); // b is not derived of C
  assert(derived_cast<D>(d.get()) != NULL); // d has exact type D
  assert(derived_cast<B>(d.get()) != NULL); // d is derived of B 
  assert(derived_cast<C>(d.get()) != NULL); // d is derived of C, too
  assert(derived_cast<D>(c.get()) == NULL); // c is not derived of D

  return 0;
}

Код:

template <typename E, typename T>
E& force_exact(const T& ref)
 {
   static const E* exact_obj;
   static const T& exact_obj_ref = *exact_obj;
   static const ptrdiff_t exact_offset = ...

не очень хорошо работает для меня, как static const E* exact_obj это ноль, поэтому статический const T& exact_obj_ref = *exact_obj нуля тоже ноль static const ptrdiff_t exact_offset становится также нулем.

Мне кажется, что производный класс должен быть создан (что может быть проблемой для абстрактных классов...). Итак, мой код:

template <typename D, typename B>
D & Cast2Derived(B & b)
{ static D d;
  static D * pD = & d;
  static B * pB = pD;
  static ptrdiff_t off = (char *) pB - (char *) pD;

  return * (D *) ((char *) & b - off);
} 

Протестировано под MSVC 2008, WinXP 32b.

Любые комментарии / лучшие решения приветствуются.

Lup

Проблема с виртуальным наследованием заключается в том, что адрес базового класса не обязательно совпадает с производным адресом. Таким образом, даже reinterpret_cast или же void* броски не помогут.

Один из способов решить эту проблему без использования dynamic_cast заключается в вычислении смещения между типом указателя (точным типом и типом ссылки), чтобы соответствующим образом изменить адрес объекта во время приведения.

 template <typename E, typename T>
 E& force_exact(const T& ref)
 {
   static const E* exact_obj;
   static const T& exact_obj_ref = *exact_obj;
   static const ptrdiff_t exact_offset =
     (const char*)(void*)(&exact_obj_ref)
     - (const char*)(void*)(exact_obj);
   return *(E*)((char*)(&ref) - exact_offset);
 }

Если у вас есть другой способ убедиться, что то, что вы делаете, является безопасным типом во время выполнения, просто используйте reinterpret_cast.

По сути, это то же самое, что и приведение стиля C, поэтому используйте его только при необходимости, но это позволит скомпилировать приведенный выше код.

Другие вопросы по тегам