Dynamic_cast 真的适用于多重继承吗?

发布于 2024-12-02 03:45:29 字数 756 浏览 0 评论 0原文

我想看看是否可以创建“接口”,继承它们,然后在运行时检查是否有任何随机类实现该接口。这就是我所拥有的:

struct GameObject {
    int x,y;
    std::string name;

    virtual void blah() { };
};

struct Airholder {
   int oxygen;
   int nitrogen;
};

struct Turf : public GameObject, public Airholder {
   Turf() : GameObject() {
      name = "Turf";
   }

   void blah() { };
};

void remove_air(GameObject* o) {
   Airholder* a = dynamic_cast<Airholder*>(o);
   if(!a) return;
   a->oxygen   = 0;
   a->nitrogen = 0;
};

现在,它有效了。文档说它有效,测试示例也有效。而且,直到我向 GameObject 添加虚拟方法后它才编译。问题是,我真的不知道该功能是否打算这样使用。让我想知道的是,我必须为我正在检查的类声明一个虚函数。但显然,没有,我正在检查的类本身没有虚函数,事实上我的整个代码与虚函数无关,这是一种完全不同的方法。

所以,我想我的问题是:如果我所做的确实有效,为什么我需要一个虚函数来为我的类提供一个虚函数表?为什么我不能将类声明为“运行时类型”或没有虚函数的类?

I wanted to see if it's possible to create "interfaces", inherit them, and then check at runtime if any random class implements that interface. This is what I have:

struct GameObject {
    int x,y;
    std::string name;

    virtual void blah() { };
};

struct Airholder {
   int oxygen;
   int nitrogen;
};

struct Turf : public GameObject, public Airholder {
   Turf() : GameObject() {
      name = "Turf";
   }

   void blah() { };
};

void remove_air(GameObject* o) {
   Airholder* a = dynamic_cast<Airholder*>(o);
   if(!a) return;
   a->oxygen   = 0;
   a->nitrogen = 0;
};

Now, it works. The documentation says that it works, the test example works.. But also, it didn't compile until I added a virtual method to GameObject. The thing is, I really don't know if the feature is intended to be used like that. What made me wonder there is the fact that I have to declare a virtual function for the class I'm checking. But obviously, there is none, the class I'm checking itself has no virtual functions, in fact my whole code has nothing to do with virtual functions, it's an entirely different approach.

So, I guess my question is: If what I'm doing really works, why do I need a virtual function to give my class a vtable? Why can't I declare the class a "runtime type" or something without virtual functions?

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

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

发布评论

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

评论(6

煮茶煮酒煮时光 2024-12-09 03:45:30

正如其他人所说,您至少需要一个虚函数才能使类具有多态性。为什么这很重要,因为dynamic_cast本身就是一个多态操作!给定一个基类指针,它会根据调用它的实际对象返回不同的结果。

C++ 有一种“不要为不需要的东西付费”的理念,因此不会提供 vtable(或编译器使用的任何机制),除非存在虚拟函数来确定需要。显然,C++ 的设计者认为这是dynamic_cast 正确操作的合理要求,否则他们会提供一种在没有它的情况下生成虚函数表的方法。

As others have said, you need at least one virtual function to make a class polymorphic. Why this matters is that dynamic_cast itself is a polymorphic operation! Given a base class pointer, it returns different results based on the actual object it is called on.

C++ has a "don't pay for what you don't need" philosophy, thus the vtable (or whatever mechanism the compiler uses) is not provided unless there's a need as determined by the presence of a virtual function. Evidently the designers of C++ thought this was a reasonable requirement for the proper operation of dynamic_cast or they would have provided a way to generate a vtable without it.

魂牵梦绕锁你心扉 2024-12-09 03:45:30

dynamic_cast 要求类型是多态的,并且如果没有任何虚拟方法(或至少一个虚拟析构函数),类型就不是(运行时)多态的。简单的继承是不够的。如果没记错的话,dynamic_cast 使用的运行时类型信息与 vtable 一起存储。

dynamic_cast requires the type to be polymorphic, and without any virtual methods (or at least a virtual destructor) a type is not (run-time) polymorphic. Simple inheritance is not enough. The run-time type information used by dynamic_cast is stored alongside the vtable if remember correctly.

你在看孤独的风景 2024-12-09 03:45:30

主要有两个原因。首先是它没有用例。继承的要点是虚函数。如果您不使用虚函数,请不要使用继承。

其次,由于 C++ 编译模型的原因,实际实现无需虚函数的 dynamic_cast 非常复杂。实际实现dynamic_cast的唯一方法是在虚拟表上进行操作——二进制数据块是无类型的。您可以定义一个类,然后仅在一个 TU 中dynamic_cast它 - 现在一个 TU 认为该类具有 vtable,而另一个 TU 则没有。那会立即很糟糕。允许在尚未具有虚函数的类上使用 dynamic_cast 就是 export,这意味着“实现起来极其困难”。

There are two main reasons. The first is that there's just no use case for it. The point of inheritance is virtual functions. If you're not using virtual functions, don't use inheritance.

The second is that it's very complex to actually implement dynamic_cast that works without virtual functions due to the C++ compilation model. The only way to realistically implement dynamic_cast is to operate on the virtual table- a binary blob of data is typeless. You could define a class and then only dynamic_cast it in one TU- now one TU thinks the class has a vtable and one doesn't. That would be instant bad. Allowing dynamic_cast on classes that do not already have virtual functions would be, well, export, which means "Exceedingly difficult to implement".

如梦亦如幻 2024-12-09 03:45:30

[编辑]根据评论(人们比我聪明得多)我的答案是完全错误的。然而,无论如何,让你的析构函数成为虚拟的。 [/编辑]

在 C++ 中,我认为只有析构函数是虚拟的,向上转换为基类型才是安全的。从技术上讲它是安全的,但实际上,您几乎总是需要一个虚拟析构函数。例如:

class Base {
   int thingy;
};
class Derived : Base{
   int *array;
   Derived() {array = new int[100];}
   ~Derived() {delete [] array;}
};
int main() {
    std::auto_ptr<Base> obj(dynamic_cast<Base*>(new Derived));
}

在此示例中,当 obj 超出范围时,auto_ptr 自动调用 Base 的析构函数,但不会调用 Derived 析构函数,因为类型是 Base,而不是 Derived。 [编辑:更正] 这会导致未定义的行为(最好的情况是,它会导致内存泄漏)。我不知道为什么 C++ 不需要虚拟析构函数来编译向下强制转换,它确实应该这样做。

[EDIT] According to the comments (people way smarter than me) my answer is completely wrong. However, make your destructors virtual anyway. [/EDIT]

In C++, I consider upcasting to a base type is only safe if the destructor is virtual. Technically it's safe, but in reality, you almost always want a virtual destructor. For instance:

class Base {
   int thingy;
};
class Derived : Base{
   int *array;
   Derived() {array = new int[100];}
   ~Derived() {delete [] array;}
};
int main() {
    std::auto_ptr<Base> obj(dynamic_cast<Base*>(new Derived));
}

In this example, when obj goes out of scope, the auto_ptr automatically calls the Base's destructor, but does not call the Derived deconstructor because the type is a Base, not a Derived. [Edit: corrections] This causes Undefined behaviour (at the very best, it causes a memory leak). I haven't any idea why C++ doesn't require a virtual destructor to compile down casts, it really should.

明月夜 2024-12-09 03:45:29

标准第5.2.7节说:

  1. 表达式dynamic_cast(v)的结果是将表达式v转换为类型的结果
    T.T 应是完整类类型的指针或引用,或“指向 cv void 的指针”。类型不得
    在dynamic_cast 中定义。 Dynamic_cast 运算符不应放弃常量性 (5.2.11)。
  2. 如果 T 是指针类型,则 v 应是指向完整类类型的指针的右值,结果是以下的右值
    类型 T。如果 T 是引用类型,则 v 应是完整类类型的左值,结果是
    T 引用的类型。
  3. 如果 v 的类型与所需的结果类型相同(为方便起见,在本例中将其称为 R)
    描述),或者与 R 相同,只是 R 中的类对象类型比类更具 cv 限定性
    v 中的对象类型,结果为 v(必要时进行转换)。
  4. 如果 v 的值在指针情况下是空指针值,则结果是类型 R 的空指针值。
  5. 如果 T 是“指向 cv1 B 的指针”并且 v 具有“指向 cv2 D 的指针”类型,则B 是 D 的基类,结果是 a
    指向 v 所指向的 D 对象的唯一 B 子对象的指针。类似地,如果 T 是“对 cv1 B 的引用”
    并且 v 具有类型“cv2 D”,使得 B 是 D 的基类,结果是 unique60) B 子对象的左值
    v 引用的 D 对象的值。在指针和引用情况下,cv1 应具有相同的 cvqualification
    as 或比 cv2 更高的 cv 限定,并且 B 应是可访问的明确基类
    D. [示例:

    结构 B {};
    结构 D : B {};
    无效 foo(D* dp)
    {
        B* bp =dynamic_cast(dp); // 等价于 B* bp = dp;
    }
    —示例结束]

  6. 否则,v 应是指向多态类型的指针或多态类型 (10.3) 的左值。

并且要使类型多态,它需要一个虚函数,根据第 10.3 条:

虚拟函数支持动态绑定和面向对象编程。声明或
继承虚函数的称为多态类。

所以原因是“因为标准是这么说的”。这并没有真正告诉你为什么标准这么说,但我认为其他答案很好地涵盖了这一点。

§ 5.2.7 of the standard says:

  1. The result of the expression dynamic_cast(v) is the result of converting the expression v to type
    T. T shall be a pointer or reference to a complete class type, or “pointer to cv void”. Types shall not be
    defined in a dynamic_cast. The dynamic_cast operator shall not cast away constness (5.2.11).
  2. If T is a pointer type, v shall be an rvalue of a pointer to complete class type, and the result is an rvalue of
    type T. If T is a reference type, v shall be an lvalue of a complete class type, and the result is an lvalue of
    the type referred to by T.
  3. If the type of v is the same as the required result type (which, for convenience, will be called R in this
    description), or it is the same as R except that the class object type in R is more cv-qualified than the class
    object type in v, the result is v (converted if necessary).
  4. If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type R.
  5. If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a
    pointer to the unique B sub-object of the D object pointed to by v. Similarly, if T is “reference to cv1 B”
    and v has type “cv2 D” such that B is a base class of D, the result is an lvalue for the unique60) B sub-object
    of the D object referred to by v. In both the pointer and reference cases, cv1 shall be the same cvqualification
    as, or greater cv-qualification than, cv2, and B shall be an accessible unambiguous base class
    of D. [Example:

    struct B {};
    struct D : B {};
    void foo(D* dp)
    {
         B* bp = dynamic_cast(dp); // equivalent to B* bp = dp;
    }
    —end example]

  6. Otherwise, v shall be a pointer to or an lvalue of a polymorphic type (10.3).

And to make a type polymorphic, it needs a virtual function, as per § 10.3:

Virtual functions support dynamic binding and object-oriented programming. A class that declares or
inherits a virtual function is called a polymorphic class.

So the reason why is "because the standard says so." That doesn't really tell you why the standard says so though, but the other answers cover that well I think.

海的爱人是光 2024-12-09 03:45:29

所以,我想我的问题是:如果我所做的确实有效,为什么我需要一个虚函数来为我的类提供一个虚函数表?为什么我不能将类声明为“运行时类型”或没有虚函数的类?

虚函数的存在使得 C++ 中的类具有多态性。 dynamic_cast<> 仅适用于多态类。 (编译器将拒绝对非多态对象进行动态转换。)

多态性在时间和空间(内存)上都有成本。对虚拟函数的调用现在是间接的,通常通过虚拟表来实现。在一些关键地方,这些成本根本无法接受。因此,该语言提供了避免这些成本的方法。

语言中的其他地方也存在类似的概念。基本原则是,如果您不想使用某些冠冕堂皇的功能,那么您不必为某些人确实想要使用它的事实付费。

So, I guess my question is: If what I'm doing really works, why do I need a virtual function to give my class a vtable? Why can't I declare the class a "runtime type" or something without virtual functions?

The presence of a virtual function is what makes a class polymorphic in C++. dynamic_cast<> only works with polymorphic classes. (The compiler will reject a dynamic cast on a non-polymorphic object.)

Polymorphism has a cost, both in time and in space (memory). Calls to virtual functions are now indirect, typically implemented in terms of a virtual table. In some critical places, those costs are simply unacceptable. So the language provides means of avoiding these costs.

Similar concepts exist elsewhere in the language. The underlying principle is that if you don't want to use some high-falutin' feature you shouldn't have to pay for the fact the some people do want to use it.

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