我现在正在学习C ++,并且我阅读了很多关于使用超类指针来指向子类的材料'对象,尤其是(纯)虚拟类的情况。由于我没有很多经验,任何人都可以帮助我理解为什么我们需要这样做吗?非常感谢!
答案 0 :(得分:4)
你不需要。如果这是你真正想要的,你可以使用指向派生类型的指针。
Liskov substitution principle表示我们应始终能够在预期基类型的任何地方使用派生类型。这个想法是派生类型不应该比它的基类更具限制性。这样,派生的是-a 基类型,并且可以在使用基类型的任何地方使用。基类型定义接口,派生类型应该满足相同的接口。如果喜欢,派生类型可以扩充界面。
您的函数应该采用的指针类型取决于您希望能够接受的内容。例如,如果您的层次结构包含两个Widget
,Button
和List
,那么如果您的函数很乐意采用任何类型的Widget
,那么它应该采用Widget*
。但是,如果该功能明确需要Button
,则应该使用Button*
。原因是该函数可能需要一些仅Button
可以提供的功能。
当通过指针调用成员函数并且编译器发现该函数是virtual
时,编译器确保使用对象的动态类型来确定要调用的函数。也就是说,假设您有一个Widget*
参数,但实际上是将指针传递给Button
对象。对象的静态类型是Widget
(如果编译器只查看参数类型),但其动态类型为Button
。如果您致电widget->draw()
,其中draw
是virtual
函数,则会看到动态类型为Button
,并确保调用Button::draw
。
但是,我不建议一般使用原始指针,所以如果可以的话,更喜欢引用(Widget&
)或智能指针。
答案 1 :(得分:2)
以下是一个例子:
struct base { virtual void do_stuff(); = 0 };
struct specialization1: base {
void do_stuff() override { std::cout << "doing concrete stuff"; }
};
请考虑您有要调用do_stuff的客户端代码。
首次实施(这就是不的方式):
void client_do_stuff( specialization1& s ) { s.do_stuff(); }
此功能有效。如果你决定(从现在起四个月)加入你的代码库:
struct specialization2: base {
void do_stuff() override { std::cout << "doing other concrete stuff"; }
};
您可能需要为void client_do_stuff
的实例调用specialization2
。您可以使用specialization2引用复制client_do_stuff
,但这是代码重复且不必要。
更好的解决方案是更改client_do_stuff
以引用基类,并对两个特化使用相同的实现。
第二次实施:
void client_do_stuff( base& b ) { b.do_stuff(); }
客户代码:
specialization1 s1;
specialization2 s2;
client_do_stuff(s1); // works
client_do_stuff(s2); // works
client_do_stuff的实现是根据基类的公共接口实现的,而不是专门化。这使得函数“面向未来”(该原理有时被称为“程序到接口,而不是实现”)。
答案 2 :(得分:0)
这个想法如下:一个对象有以下接口(纯虚拟类)。我将把一个具体的对象交给你的代码,这个代码遵循这个接口,但是我将保留给自己的所述对象的内部细节(封装)。因此,您的代码不能对对象的精确大小等做出任何假设。因此,在编译代码时,必须在操作对象时使用指针或引用。