2010-07-27 2 views
14

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

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

Любые идеи?

+0

Хех, хорошо MS просто говорит, чтобы удалить из ключевого слова виртуальное ключевое слово, и оно решит проблему. Посмотрите их документацию на ошибку, я не шучу. –

+1

Кто, черт возьми, сказал, что Android - это платформа, отличная от rtti? R5 и новый NDK должны поддерживать RTTI (я считаю, что вам нужно включить его с помощью '-frtti', но он должен работать тогда). Даже для более старых платформ, поскольку все это статически связано. –

ответ

12

Вы можете сделать это с помощью только dynamic_cast; никакая другая акция не сделает этого.

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

E.g. (Ужасно Hacky)

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; } 
}; 
+3

Я не знаю, стоит ли минус или добавить для этого ответа. –

+2

@Noah: В моей защите я сказал «ужасно хаки»! –

+2

@Noah: хакерская версия выглядит лучше, чем литая матрица, которую я видел в одном проекте давно. на системе rtti-less люди не нашли ничего лучшего, чем дать каждому классу уникальный идентификатор и использовать id в качестве индекса в двухмерную матрицу. matrix [cast_what] [cast_to] - это идентификатор класса, результат dynamic_cast. – Dummy00001

-2

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

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

+1

Ну, я пробовал, но у меня появилось предупреждающее сообщение (4946: reinterpret_cast, используемое между связанными классами: «class1» и «class2»). Обратите внимание, что предупреждение по умолчанию отключено. Я считаю, что reinterpret_cast просто переинтерпретирует указатель (как он говорит), но он не бросает его, и он вызывает проблемы, когда есть виртуальные наследования или несколько родителей, поскольку приведение к одному из родителей может изменить значение указателя. Таким образом, reinterpret_cast не будет работать в моем случае, когда сторонний я использую широкое использование этих понятий (несколько родителей, наследование вирусов и т. Д.). – Adam

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

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

Боковое примечание: Объявление смещения сделано статичным, поскольку оно должно вычисляться только один раз для каждой пары T, E в теории. Однако на практике использование 'static' имеет стоимость выполнения (переписывается как' if (! Var_initilized) var_ = ... '). Один из способов избежать этого - создать экземпляр класса шаблона, который статически сохраняет значение смещения для каждой пары T, E, но это еще одна история ... – log0

1

код:

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 ноль, тоже, и, таким образом, становится 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

Это проблематично, если конструктор D выполняет побочные эффекты. Возможно, лучшим решением является reinterpret_cast соответствующий размерный и выровненный буфер символов в D-указатель. Кроме того, чтобы сохранить с C++ листинг имен, как насчет именования его 'virtual_cast (B *)'? Я еще не подумал об этом, но существуют ли иерархии наследования, для которых это не удастся? Возможно, класс, который наследует один и тот же виртуальный класс несколько раз (даже если косвенно) или, возможно, с комбинацией не виртуального наследования для загрузки. Во всяком случае +1 –

+0

Привет, Томас, не могли бы вы лучше описать свою идею о переосмыслении литья буфера символов в D *? – Juster

4

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^