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

В настоящее время я работаю над интеграцией стороннего пакета, который использует множество 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: «инициализация»: невозможно преобразовать из «test_convert :: A *» в «test_convert :: D *» Приведение из базы в производное требует dynamic_cast или static_cast

Есть идеи?

Ответы [ 6 ]

12 голосов
/ 28 июля 2010

Вы можете делать это только с 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; }
};
5 голосов
/ 19 августа 2011

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

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

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

3 голосов
/ 28 июля 2010

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

код:

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

0 голосов
/ 13 мая 2011

Проблема с виртуальным наследованием заключается в том, что адрес базового класса не обязательно такой же, как производный адрес. Таким образом, даже 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);
 }
0 голосов
/ 28 июля 2010

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

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

...