С++ - downcasting наследуемый объект формы алмаза без RTTI/dynamic_cast

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

Проблема заключается в том, что у многих классов есть проблема наследования алмазов, поскольку все классы производятся из одного и того же базового класса (объекта).. и поэтому, если я хочу сбрасывать из базового класса в производный класс, я должны использовать 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: 'initializing': невозможно преобразовать из 'test_convert:: A *' в 'test_convert:: D *'        Для трансляции из базы в производную требуется dynamic_cast или static_cast

Любые идеи?

Ответы

Ответ 1

Вы можете сделать это с помощью 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; }
};

Ответ 2

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

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

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

Ответ 3

В большинстве случаев шаблон посетителя может использоваться, чтобы избежать опускания. Его также можно использовать, чтобы избежать 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;
}

Ответ 4

код:

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 derefs zero тоже, и, таким образом, 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

Ответ 5

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

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

 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);
 }

Ответ 6

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

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