为什么我们需要一个超类指针来指向一个子类的对象?

时间:2014-07-03 10:44:01

标签: c++

我现在正在学习C ++,并且我阅读了很多关于使用超类指针来指向子类的材料'对象,尤其是(纯)虚拟类的情况。由于我没有很多经验,任何人都可以帮助我理解为什么我们需要这样做吗?非常感谢!

3 个答案:

答案 0 :(得分:4)

你不需要。如果这是你真正想要的,你可以使用指向派生类型的指针。

Liskov substitution principle表示我们应始终能够在预期基类型的任何地方使用派生类型。这个想法是派生类型不应该比它的基类更具限制性。这样,派生的是-a 基类型,并且可以在使用基类型的任何地方使用。基类型定义接口,派生类型应该满足相同的接口。如果喜欢,派生类型可以扩充界面。

您的函数应该采用的指针类型取决于您希望能够接受的内容。例如,如果您的层次结构包含两个WidgetButtonList,那么如果您的函数很乐意采用任何类型的Widget,那么它应该采用Widget*。但是,如果该功能明确需要Button,则应该使用Button*。原因是该函数可能需要一些仅Button可以提供的功能。

当通过指针调用成员函数并且编译器发现该函数是virtual时,编译器确保使用对象的动态类型来确定要调用的函数。也就是说,假设您有一个Widget*参数,但实际上是将指针传递给Button对象。对象的静态类型是Widget(如果编译器只查看参数类型),但其动态类型为Button。如果您致电widget->draw(),其中drawvirtual函数,则会看到动态类型为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)

这个想法如下:一个对象有以下接口(纯虚拟类)。我将把一个具体的对象交给你的代码,这个代码遵循这个接口,但是我将保留给自己的所述对象的内部细节(封装)。因此,您的代码不能对对象的精确大小等做出任何假设。因此,在编译代码时,必须在操作对象时使用指针或引用。