我刚开始学习多态性,被史密斯卡住了。我有一个基类Object
,和一个派生类Ball
。我想实现多态性,并在Object
指针上使用Ball
方法:
Object *ball = new Ball(P, dPdt, s, Forces);
cout << ball->getP() << " positionn" << ball->getdPdt() << " speedn";
第一个,getP()
,工作得很好,因为它是Object
类的方法,Ball
的构造函数调用Object
的构造函数来初始化它。现在,当谈到getdPdt()
时,编译器抱怨在Object
中没有名为getdPdt
的成员,这是显而易见的,因为我在Ball
中定义了它。我在这里做错了什么/理解错了什么?
PS:我需要*ball
成为Object
,因为这是一个物理模拟,一切都必须是Object
,以便模拟它。稍后,我想我会添加一个vector<unique_ptr<Object>>
来跟踪
c++是一种静态类型语言。如果表达式的静态类型是Object
,那么它可以做Object
可以做的任何事情(没有其他)。如果它是Ball
,那么它可以做Ball
能做的任何事情。您希望它是Object
,但能够做Ball
可以做的事情。你不能两者兼得,你必须做出选择。
多态性并不是让专门为Ball
定义的属性通过Object
指针神奇地可用。它是关于Ball
属性继承自Object
的行为在Ball
特定的方式。
注意
Object *ball = new Ball(P, dPdt, s, Forces);
dynamic_cast<Ball*>(ball)->getdPdt();
只能这样读:
Object *ball = new Ball(P, dPdt, s, Forces);
// I want to create a Ball but forget that it's a Ball
// I want to only remember that it's an Object
dynamic_cast<Ball*>(ball)->getdPdt();
// No! It's really a Ball after all!
// My decision to forget it was wrong! I regret it!
现在任何人看到这两行代码都会怀疑你是否真的知道你想要什么,这是正确的。不要做让你后悔莫及的事。
如果这两行不相邻,情况会更复杂。例如,您可能有一个向量Object*
:
std::vector<Object*> objects;
objects.push_back(new Ball(whatever));
// in a totally different function in a totally different file
dynamic_cast<Ball*>(objects[i])->getdPdt();
现在,机敏的读者会问的问题是不同的,但同样困难。为什么知道objects[i]
指向Ball
?为什么这些知识没有被编码到变量的类型中?这就是c++中类型的作用。回想一下,c++是一种静态类型语言。任何例外(dynamic_cast
自然是例外,因为它有"动态"这个词;它应该是非常合理的。如果有一些代码根据变量的类型决定要做什么,那么这些代码通常应该是基类中的一个方法。
最后,在保持面向对象设计范式时,您只能做两件不同的事情。或者将getdPdt
添加到Object
,以便所有Object
都可用;或者把你的Ball
放在一个单独的篮子里,这个篮子的类型表明里面有Ball
,而不仅仅是Object
。dynamic_cast
偏离了OO范式。一种偏差是否可以被你接受取决于你自己,但是如果你发现自己经常偏离它,那么也许你需要考虑一个不同的范式。
如果Object
是多态类型,一个快速而肮脏的解决方案是向下转换。
Object *ball = new Ball(P, dPdt, s, Forces);
dynamic_cast<Ball*>(ball)->getdPdt();
此操作仅在您确定ball
指向Ball
对象时才有效。否则,必须在对强制转换的指针调用getdPdt
之前进行检查。
注意,上面的解决方案违背了您想要实现的多态性的目标。多态性是指为不同类型的实体(在本例中为Object
类)提供单个接口(在本例中为Object
的所有子类,包括Ball
)。接口的设计应该使所有必需的操作都可以通过Object*
访问。
如果为Object
定义getdPdt
有意义,则更好的多态解决方案:
class Object {
public:
virtual int getdPdt() const {
return 0;
}
};
class Ball : public Object {
public:
int getdPdt() const override {
return 42;
}
};
int main() {
Object* obj = new Ball();
// this returns 42, despite obj being `Object*`
obj->getdPdt();
}