Dynamic_cast 真的适用于多重继承吗?
我想看看是否可以创建“接口”,继承它们,然后在运行时检查是否有任何随机类实现该接口。这就是我所拥有的:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
正如其他人所说,您至少需要一个虚函数才能使类具有多态性。为什么这很重要,因为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.
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 bydynamic_cast
is stored alongside the vtable if remember correctly.主要有两个原因。首先是它没有用例。继承的要点是虚函数。如果您不使用虚函数,请不要使用继承。
其次,由于 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 implementdynamic_cast
is to operate on the virtual table- a binary blob of data is typeless. You could define a class and then onlydynamic_cast
it in one TU- now one TU thinks the class has a vtable and one doesn't. That would be instant bad. Allowingdynamic_cast
on classes that do not already have virtual functions would be, well,export
, which means "Exceedingly difficult to implement".[编辑]根据评论(人们比我聪明得多)我的答案是完全错误的。然而,无论如何,让你的析构函数成为虚拟的。 [/编辑]
在 C++ 中,我认为只有析构函数是虚拟的,向上转换为基类型才是安全的。从技术上讲它是安全的,但实际上,您几乎总是需要一个虚拟析构函数。例如:
在此示例中,当 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:
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.
标准第5.2.7节说:
T.T 应是完整类类型的指针或引用,或“指向 cv void 的指针”。类型不得
在dynamic_cast 中定义。 Dynamic_cast 运算符不应放弃常量性 (5.2.11)。
类型 T。如果 T 是引用类型,则 v 应是完整类类型的左值,结果是
T 引用的类型。
描述),或者与 R 相同,只是 R 中的类对象类型比类更具 cv 限定性
v 中的对象类型,结果为 v(必要时进行转换)。
如果 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;
}
—示例结束]
并且要使类型多态,它需要一个虚函数,根据第 10.3 条:
所以原因是“因为标准是这么说的”。这并没有真正告诉你为什么标准这么说,但我认为其他答案很好地涵盖了这一点。
§ 5.2.7 of the standard says:
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).
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.
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).
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]
And to make a type polymorphic, it needs a virtual function, as per § 10.3:
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.
虚函数的存在使得 C++ 中的类具有多态性。
dynamic_cast<>
仅适用于多态类。 (编译器将拒绝对非多态对象进行动态转换。)多态性在时间和空间(内存)上都有成本。对虚拟函数的调用现在是间接的,通常通过虚拟表来实现。在一些关键地方,这些成本根本无法接受。因此,该语言提供了避免这些成本的方法。
语言中的其他地方也存在类似的概念。基本原则是,如果您不想使用某些冠冕堂皇的功能,那么您不必为某些人确实想要使用它的事实付费。
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.