没有运行时类型信息的dynamic_cast

4

我有一个如下所示的结构:

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,用于代码的其他部分。
鉴于此,是否有一种常见的模式来解决侧面-下转换问题,而不使用dynamic_cast和RTTI的需要,假设我已经知道一个MyType类继承自特定的trait?我正在尝试找到一个聪明的解决方案,因为它是代码的重要瓶颈。

在基类中,您可以添加数十个(数百个?)getter-caster,例如virtual Foo* AsFoo() { return nullptr; },然后在Foo中放入Foo* AsFoo() override { return this; } - Eljay
你怎么知道代码有严重的瓶颈?你是否进行了没有RTTI的分析以验证它? - marcinj
@marcinj:我无法在没有dynamic_cast的情况下对其进行分析,因为我没有解决方案,但是我已经对dynamic_cast所花费的时间进行了分析,所以我已经确定它是一个相当大的瓶颈。 - Jack
你是否考虑过用std::unique_ptr替换wrapper?这意味着你可以在那些(理想情况下很少的)需要确切派生类型的地方使用std::unique_ptr<MyType1>std::unique_ptr<MyType2>,并且可以始终将其转换为std::unique_ptr<managed_object>以供通用代码使用,从而实现完全的类型安全。 - alter_igel
似乎奇妙地重复模板模式(CRTP)是正确的选择。 - seleciii44
2个回答

5

在没有运行时类型信息的情况下,dynamic_cast是不能使用的,但有少数几种特殊情况除外。

虽然您可以使用static_castreinterpret_cast(请不要这样做),但如果操作错误,则需要由您自己承担责任——此时您将无法测试是否成功转换为nullptr


这就是关键,我根本不想使用dynamic_cast,如果我的问题没有表达清楚。我想找到一种不需要dynamic_cast和RTTI的解决方案。 - Jack
实际上,只要不需要运行时查找,就可以这样做。也就是说,将对象转换为基类或将对象转换为相同类的常量限定版本都可以。 - user7860670
static_cast 无法将 managed_object 强制转换为 trait1。这就是 OP 所说的“side-downcast 问题”。reinterpret_cast 可以,但之后修改对象是未定义行为,因为两种类型没有关联。 - Cássio Renan

1
首先,你必须使用 `static_cast`,`reinterpret_cast` 并不适合此情况。
但是为了使转换工作,你的程序需要知道它要去哪里。也就是说,它需要知道从 A 到 B 进行转换所需的路径。如果 A 和 B 在同一类层次结构中,那么这很简单:只需按照该类层次结构进行转换即可。
但是,如果你有以下情况:
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的说明
如果您正在从类转换为其基类(从MyType到trait1),dynamic_cast将返回派生对象中的基类子对象的指针或引用(static_cast也可以正确地进行此转换)。这意味着返回的指针的值实际上可能与提供的指针的值不同。reinterpret_cast永远不会对指针值进行此类更改。它只会将传递给它的任何内容重新解释为新类型,这显然是错误的。明显的结论是不要在类层次结构内使用reinterpret_cast执行转换。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接