【问题标题】:dynamic_cast without RTTI没有 RTTI 的 dynamic_cast
【发布时间】:2018-10-26 18:10:06
【问题描述】:

我的结构如下:

struct managed_object {
  virtual ~managed_object() { }
};

class trait1 {
  public: 
    virtual void myMethod() const = 0;
};

class trait2 {
  public: 
    virtual void myOtherMethod(int x) const = 0;
};

class MyType final : public managed_object, public trait1 {
 ...
};

class MyType2 final : public managed_object, public trait1, public trait2 {
 ...
};

class wrapper {
  private:
    managed_object* ptr;
  public:
    template<typename T> T* object() const { 
      return dynamic_cast<T*>(data.ptr); 
    }
};

所以基本上我有一个managed_object 基类,多个类型从该基类继承。这些子类型中的每一个都可以从任何特征组合继承,它们是final,所以我确信它们不会有更深层次的继承。

代码的工作要归功于 RTTI,它承担了将所有东西粘合在一起的负担,但要付出代价,否则

wrapper w = ...
trait* asTrait1 = w.object<trait1>;

不起作用,因为 managed_objecttrait1 类型之间没有直接关系。

在我的完整代码中,我已经确定所有 dynamic_cast 都不会失败,因为我有额外的数据(示例中未显示),它为我提供了代码其他部分所需的一种 RTTI。

鉴于此,假设我已经知道 MyType 类继承自一个特定的trait?我正在尝试找到一个聪明的解决方案,因为它是代码的严重瓶颈。

【问题讨论】:

  • 在基类中,您可以添加数十个(数百个?)像virtual Foo* AsFoo() { return nullptr; } 这样的getter-casters,然后在Foo 中放入Foo* AsFoo() override { return this; }
  • 你怎么知道它是代码的严重瓶颈?您是否在没有 RTTI 的情况下进行了分析来验证它?
  • @marcinj:没有dynamic_cast,我无法分析它,因为我没有解决方案,但是是的,我分析了在dynamic_cast-ing 中花费了多少时间,所以我已经确定它是相当的瓶颈
  • 您是否考虑将wrapper 替换为std::unique_ptr?这意味着您可以在精确派生类型很重要的那些(理想情况下为少数)地方使用具有完全类型安全性的 std::unique_ptr&lt;MyType1&gt;std::unique_ptr&lt;MyType2&gt;,然后您始终可以将其转换为 std::unique_ptr&lt;managed_object&gt; 用于泛型代码。跨度>
  • 似乎奇怪的重复模板模式(CRTP)是要走的路。

标签: c++ c++14 multiple-inheritance rtti dynamic-cast


【解决方案1】:

不能在没有 RTTI 的情况下使用 dynamic_cast。除了少数极端情况。

可以使用static_castreinterpret_cast(请不要),但是如果你弄错了,那就是 - 那么你不能对nullptr 进行更长时间的测试以查看转换是否成功

【讨论】:

  • 这就是重点,如果我的问题不清楚,我根本不想使用dynamic_cast。我想找到一个不需要dynamic_cast 和 RTTI 的解决方案。
  • 其实只要不需要运行时查找就可以。即转换为基类或转换为同一类的 const 限定版本都可以。
  • static_cast 不会将 managed_object 转换为 trait1。这就是 OP 所说的“side-downcast 问题”。 reinterpret_cast会,但是后面修改对象是UB,因为这两种类型没有关系。
【解决方案2】:

第一件事:你必须使用static_castreinterpret_cast 不太适合这个。

但是为了使演员表工作,您的程序需要知道它的去向。我的意思是,它需要知道从A 转换到B 必须经过的路径。如果AB 在同一个类层次结构中,这很简单:只需按照所述类层次结构执行转换即可。

但如果你有:

struct C: A, B {};

这是AB之间的唯一关系,static_cast无法知道C(这是RTTI提供的那种信息),因此它无法执行转换,因为AB 并不真正相关。

为了提供该路径,您必须让您的程序以某种方式知道它。最简单的方法是模板化wrapper

template<typename T>
class wrapper {
  managed_object* ptr;
public:
  template<typename Trait> Trait* object() const { 
    return static_cast<Trait*>(static_cast<T*>(ptr)); 
  }
};

然后:

MyType a;
wrapper<MyType> w{&a};
trait1* asTrait1 = w.object<trait1>(); // OK

请注意,我确切地告诉了如何进行转换,首先向下转换为派生类型,然后“向上转换”回特征。

关于reinterpret_cast的注释

如果您从一个类转换为它的基类(MyTypetrait1),dynamic_cast 将返回派生对象中基类子对象的指针或引用(@ 987654341@也可以正常进行这种转换)。这意味着返回的指针的值实际上可能与提供的指针的值不同。 reinterpret_cast 永远不会对指针值进行这样的更改。它只会将传递给它的任何内容重新解释为新类型,这显然是错误的。显而易见的结论是不要使用reinterpret_cast 在类层次结构中执行强制转换。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-14
    • 2016-11-05
    • 2016-04-02
    相关资源
    最近更新 更多