【问题标题】:How to design a dynamic type system without the RTTI?如何设计一个没有 RTTI 的动态类型系统?
【发布时间】:2017-11-28 12:22:21
【问题描述】:

我知道这个问题有点宽泛和不精确,但这就是我想要的:

struct DynTyped { /* HOW? */ };
class Animal : public DynTyped {};
class Dog : public Animal {};
class Cat : public Animal {};

// expect to work
Animal* a1 = new Dog;
Dog* d1 = DOWNCAST<Dog*>(a1);
d1->hello();

// expect to throw
Animal* a2 = new Cat;
Dog* d2 = DOWNCAST<Dog*>(a2);
d2->hello();

我可以做到的一种方法是在基类中设置一个字段。

class Animal {
enum AnimalType { Dog, Cat };
protected:
AnimalType type_;
}

但我想要一种在更高的基类中实现它的方法,因为我可能有一个形状基类,我不想再做那种类型字段的把戏了。

【问题讨论】:

  • 使用 RTTI:DOWNCAST 只是 dynamic_cast 并在您的第二个示例中返回 nullptr => UB
  • 没有 RTTI:DOWNCAST 仍然是 dynamic_cast 但这次返回 a2 cast => UB
  • 我想在没有 RTTI 的情况下投掷。
  • Here's one way。但我确实警告你,它需要你的样板。很多样板。
  • @StoryTeller 太糟糕了!官方 LLVM 文档中的 that 怎么样?

标签: c++ types casting rtti


【解决方案1】:

它不是一个完美的替代品,但是,CRTP 有你想要的潜力......

顺便说一句,您为什么要避免 RTTI?无法模拟 RTTI。

【讨论】:

  • 我的项目经理决定避免它。它与 RTTI 无关,我不想要一个完整的 RTTI 实现,我只想安全地转换为一个类型。
  • CRTP是怎么做到的?
  • 英文资料不太懂,不过日文资料是hre(请用谷歌翻译)qiita.com/Riyaaaa_a/items/a9af401520f238f45b80
  • 我看了这篇文章,但我还是不知道怎么做,我认为我的问题与动态字段值有关。
  • 动态字段值必须在运行时解析。这意味着使用带有肮脏错误实现的虚拟或重现 vtable。要使用 CRTP,请将逻辑更改为静态。因此,最简单、容易、无故障的首选方法是使用虚拟和 RTTI。
【解决方案2】:

这可能看起来微不足道,但您可以这样做:

class Dog;
class Cat;
struct DynTyped { virtual Dog*AsDog() {return 0;} virtual Cat*AsCat() {return 0;};
class Animal : public DynTyped {};
class Dog : public Animal {virtual Dog*AsDog() {return this;};};
class Cat : public Animal {virtual Cat*AsCat() {return this;};}

【讨论】:

  • 不错的把戏,但恕我直言,我的更好:)。你需要知道基类中的所有野兽,你还增加了vtable 的大小。从理论上讲,您需要 O(N^2) 内存来支持 N 个类。
  • 视情况而定。如果您有“class GrandRetriever:public Dog”,您可以在我提出的解决方案中添加“AsGrandRetriever()”;并保留“AsDog”。您的解决方案自然无法处理这种情况。
【解决方案3】:

首先,您需要向基类添加一个虚函数,它将在运行时识别对象的实际类型。然后你可以创建一个模板包装函数来封装实际的类型转换。

我的意思是:

class Animal {
public:
    virtual int get_Type() const = 0;
};
class Dog : public Animal {
public:
    static const int s_Type = 1;
    virtual int get_Type() const { return s_Type; }
};
class Cat : public Animal {
public:
    static const int s_Type = 2;
    virtual int get_Type() const { return s_Type; }
};

template <class T>
T* DynamicCast(Animal* p)
{
    return (T::s_Type == p->get_Type()) ? static_cast<T*>(p) : NULL;
}

这就是想法。你可以使用任何你喜欢的类型来发现实际的对象类型,在这个例子中我使用了int,你可以为此创建一个enum

还要注意,这在类型匹配的情况下有效,相反,如果目标类型是继承的,RTTI 也有效,即源对象可能是派生类型。 这是因为 RTTI 信息更复杂,它包含整个层次结构。

【讨论】:

  • @PasserBy:是的。
  • 有没有办法避免虚方法?
  • @SunYi-Ming:你必须在你的对象中有一些类型信息,这是不可避免的。您可以将成员添加到标识类型的基类,但这是较差的解决方案,因为该成员是每个对象的。 OTOH 添加虚函数是每个类的。
  • @valdo 没有区别。在虚函数的情况下,每个对象都有一个隐藏的成员变量,其中包含 vtable-pointer。
  • @Johan:当您添加第一个虚拟功能时,这是真的。但是,如果您的对象已经拥有它们 - 则不会向 obj 添加任何内容。
【解决方案4】:

一种方法是使用虚函数进行强制转换。你可以这样做:

#include <exception>

class Animal;
class Dog;
class Cat;

struct InvalidConversion : public std::exception
{
};

struct DynTyped { 
  virtual Animal *IsAnimal() { throw InvalidConversion(); }
  virtual Dog *IsDog() { throw InvalidConversion(); }
  virtual Cat *IsCat() { throw InvalidConversion(); }
};

class Animal : public DynTyped 
{
  virtual Animal *IsAnimal() override { return this; }
};

class Dog : public Animal
{
  virtual Dog *IsDog() override { return this; }
};

class Cat : public Animal
{
  virtual Cat *IsCat() override { return this; }
};

然后使用它:

// expect to work
Animal* a1 = new Dog;
Dog* d1 = a1->IsDog();
d1->hello();

// expect to throw
Animal* a2 = new Cat;
Dog* d2 = a2->IsDog();
d2->hello();

【讨论】:

    猜你喜欢
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-11
    • 2020-11-30
    • 2016-04-02
    • 2019-05-20
    • 1970-01-01
    相关资源
    最近更新 更多