C++ - 在没有 RTTI/dynamic_cast 的情况下向下转换菱形继承对象

发布于 2024-09-11 11:44:55 字数 919 浏览 2 评论 0原文

我目前正在致力于在非 RTTI 平台(Android)上集成一个使用大量 RTTI 内容的第三方包。基本上,我做了自己的 RTTI 实现,但遇到了一个问题。

问题是很多类都存在钻石继承问题,因为所有类都派生自同一个基类(对象)。因此,如果我想从基类向下转换为派生类,我必须使用动态_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

有什么想法吗?

I'm currently working on integrating a third-party package that uses lots of RTTI stuff on a non-RTTI platform (Android). Basically, I did my own RTTI implementation but I'm stuck on a problem.

The issue is that a lot of classes are having the diamond inheritance problem since all the classes derive from the same base class (object).. and so, if I want to downcast from the base class to the derived class, I have to use a dynamic_cast - but RTTI is not available! How do I convert an object from parent to child when there are virtual inheritance without dynamic_cast?

It looks like that:

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...

Those are my errors:

error C2635: cannot convert a 'A*' to a 'D*'; conversion from a virtual base class is implied

error C2440: 'initializing' : cannot convert from 'test_convert::A *' to 'test_convert::D *'
Cast from base to derived requires dynamic_cast or static_cast

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

-黛色若梦 2024-09-18 11:44:55

您只能使用 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; }
};

You can only do this cast with dynamic_cast; no other cast will do this.

If you can't design your interfaces so that you don't need to perform this type of cast then the only thing you can do is make the casting functionality part of your class hierarchy.

E.g. (horribly 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; }
};
呆橘 2024-09-18 11:44:55

Android 确实支持 RTTI。您需要最新的 NDK(至少是 r5,最新的是 r6),并且需要针对 GNU stdlibc++ 而不是默认编译。

甚至在此之前,CrystaX 的重建确实支持异常和 rtti(我们必须使用它,直到官方 NDK r5c,因为 r5a 和 r5b 有支持,但在较旧的(2.3 之前)系统上崩溃了)。

PS:有人真的应该禁止供应商在不支持异常和 rtti 的情况下说他们支持 C++,因为大多数标准库(C++ 标准的一部分)没有这两者就无法工作。另外,不支持它们是愚蠢的,特别是对于异常,因为带有异常的代码比没有异常的代码更有效(前提是它们正确地用于表示异常情况)。

Android does support RTTI. You need latest NDK (at least r5, latest is r6) and you need to compile against the GNU stdlibc++ instead of the default.

Even before, there was the CrystaX's rebuild which did support exceptions and rtti (we had to use that until official NDK r5c because r5a and r5b had the support, but crashed on older (pre-2.3) systems).

PS: Somebody should really forbid vendors say they support C++ when they don't support exceptions and rtti, because most of standard library, and that's part of the C++ standard, does not work without either. Plus not supporting them is silly, especially for the exceptions, because code with exceptions is more efficient than one without (provided they are properly used to signal exceptional cases).

久夏青 2024-09-18 11:44:55

在大多数情况下,访问者模式可用于避免沮丧。它也可以用来避免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;
}

In most cases the visitor-pattern can be used to avoid downcasts. It can be used to avoid dynamic_cast, too.

Some caveats:

1) It must be possible to change the offending classes.
2) You may need to know EVERY derived class.
3) The objects must be known to derive from at least the baseclass, you cannot try to cast completely unrelated types. (This seems to be fulfilled: "I want to downcast from the base class to the derived class")

In the following example i used templates. These can be easily get rid off, but would require quite some writing effort.

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;
}
贱贱哒 2024-09-18 11:44:55

代码:

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* excact_obj 为零,所以 static const T& excact_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下测试。

欢迎任何意见/更好的解决方案。

卢普

the code:

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 = ...

doesn't work very well for me as static const E* exact_obj is zero, so static const T& exact_obj_ref = *exact_obj derefs zero, too, and thus static const ptrdiff_t exact_offset becomes also zero.

It seems to me that the derived class needs to be instantiated (which may be a problem for abstract classes...). So my code is:

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

Tested under MSVC 2008, WinXP 32b.

Any comments / better solution(s) are welcome.

LuP

冷默言语 2024-09-18 11:44:55

虚拟继承的问题在于基类地址不一定是
与派生地址相同。因此,即使 reinterpret_castvoid* 转换也会
没有帮助。

不使用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);
 }

The problem with virtual inheritance is that the base class address is not necessarily
the same as the derived address. Thus, even reinterpret_cast or void* casts will
not help.

One way to solve this without using dynamic_cast is to compute the offset between both pointer type (the exact type and ref type) in order to modify the object address accordingly during the 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);
 }
夏花。依旧 2024-09-18 11:44:55

只要你有另一种方法来确保你所做的事情在运行时是类型安全的,就可以使用reinterpret_cast。

它基本上与 C 风格转换相同,因此只有在必要时才使用它,但它允许上面的代码编译。

As long as you have another way to make sure what you're doing is type safe at runtime, just use reinterpret_cast.

It's basically the same thing as a C style cast so only use it if you have to, but it will allow the code above to compile.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文