【问题标题】:Why are virtual functions preferable over derived class objects?为什么虚函数优于派生类对象?
【发布时间】:2020-06-22 18:24:05
【问题描述】:

所以我是学习虚拟函数的新手,我正在关注在线教程,但我似乎无法找到我的问题的答案。我想问为什么通过设置基类对象指向派生类对象来使用下面的虚函数,比只使用派生类对象本身来访问函数更好?

似乎无论哪种方式我都得到相同的输出,而且创建基类对象和虚函数似乎是额外的步骤。我在一个在线教程中看到了一个与此类似的示例,它声称虚拟函数使编码更容易,但我不太明白这个示例的好处?

我在网上看到:

虚函数的主要优点是它们直接支持面向对象的编程。当您将函数声明为虚拟函数时,您是在说执行的代码具体取决于您调用它的对象的类型。

但是使用派生对象似乎已经是这种情况,并且没有必要创建基类对象?我确定我遗漏了一些明显的东西,所以我非常感谢任何帮助。我展示了我在下面编写的示例代码,它与我在详细介绍虚拟函数时看到的类似:

#include <iostream>

using namespace std;

//////////////////////////////////////////////////////
//base class
class Shape {

public:
virtual void draw()=0;  //pure virtual function
};

//derived classes
class Square : public Shape {

public:
void draw() {
    cout << "Draw square" << endl;
    }

};

class Circle : public Shape {

public:
void draw() {
    cout << "Draw circle " << endl;
    }

};

//////////////////////////////////////////////////////
int main()
{
Square so;  //create derived class objects
Circle co;

Shape* shape1 = &so;  //setting base class objects as pointers to derived objects
Shape* shape2 = &co;


shape1->draw();  //using base class objects to access derived class
shape2->draw();

so.draw();  //using derived class objects
co.draw();

}

【问题讨论】:

  • 给定Shape *create()create()-&gt;draw() 会输出什么?假设create来自运行时加载的外部DLL。
  • 继承自具有虚函数的基类的类也是派生的。非虚拟函数不支持虚拟调度,因此您不能通过基类指针/引用调用派生类的函数,而这些函数不是虚拟的。
  • 您是否尝试过没有virtual 的相同示例?请注意,您必须实现 Shape::draw 才能尝试此操作。 virtual 的目的应该立即明确。
  • 因为这个例子太简单了以至于没有抓住重点。它应该使用一个函数来代替 Shape*Shape&amp; 参数来显示有用性。
  • 我决定创建一个名为Triangle 的新Shape。任何采用Shape 的函数都可以自动使用我的新类。

标签: c++ virtual-functions


【解决方案1】:

使用基类指针类型和虚函数的巨大好处是,您可以拥有一个包含多种不同类型的Shape 的列表,并且您可以在一个函数中处理它们由于它们的派生类型而具有不同的行为。

例如,我通过添加函数DrawAllShapes 来修改您的代码,该函数接受vector&lt;Shapes*&gt;&amp;。 (小心使用原始指针。你真的应该使用vector&lt;std::unique_ptr&lt;Shape&gt;&gt;&amp;here 或类似的东西。

您可以通过这种模式获得难以置信的灵活性,它允许您在基类指针对象的集合上调用相同的函数,但会导致集合中每个对象的行为不同,具体取决于其派生类型。

#include <iostream>
#include <vector>
using namespace std;

//////////////////////////////////////////////////////
//base class
class Shape {

public:
    virtual void draw() = 0;  //pure virtual function
};

//derived classes
class Square : public Shape {

public:
    void draw() {
        cout << "Draw square" << endl;
    }

};

class Circle : public Shape {

public:
    void draw() {
        cout << "Draw circle " << endl;
    }

};

void DrawAllShapes(std::vector<Shape*>& shapes) {
    for (int i = 0; i < shapes.size(); ++i) {
        shapes[i]->draw();
    }
}

//////////////////////////////////////////////////////
int main()
{
    std::vector<Shape*> shapeVec{ new Square, new Circle, new Square, new Square, new Circle };
    DrawAllShapes(shapeVec);

    system("pause");
}

另外,想象一下,如果您使用的是已经定义了 Shape 类和多个形状的预构建图形库。如果您想添加自己的新型 Shape 并使其与库的所有功能完美配合,该怎么办?您所要做的就是创建自己的派生类并实现库的 Shape 类公开的所有必要的虚函数,然后您就可以将库扩展至超出其原始功能的范围。

【讨论】:

  • 因此,根据我对您的回答的理解,好处是需要对象作为输入的函数可用于使用虚函数和基类指针对象访问每个派生类,而不必创建新的每种派生类对象的函数?
  • @Andrew 是的,没错。任何在基类指针集合上操作的外部代码都不必知道派生类的任何细节。它可以简单地使用基类中暴露的虚成员函数对每个实例进行操作,并且编译器已经设置代码自动将这些基类虚函数调用转发到适当的派生类。
  • @Andrew 另外,想象一下,如果您使用的是一个预构建的图形库,它有一个 Shape 类和几个定义的形状。如果您想添加自己的新型 Shape 并使其与库的所有功能完美配合,该怎么办?您所要做的就是创建自己的派生类并实现库的 Shape 类公开的所有必要的虚函数,然后您就可以从字面上扩展库,使其超出其原始功能。
  • 那么动态多态允许简单的代码扩展?最后,用户能否确定与基类指针对象相关联的派生类的类型,即用户在您的示例中决定数组的内容?我可以看到这将使代码变得非常灵活。
  • @Andrew 是的,用户可以完全控制它。此外,如果您有一个特殊用例,您需要访问属于派生类的非虚拟函数,并且如果您在运行时知道或检查基类指针引用的派生类型,那么您可以将基指针转换为派生指针并访问派生类的特定属性。有时这是必需的,但请尽量避免这种做法,因为您最终可能会得到充斥着无法充分利用多态性的类型转换的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
  • 1970-01-01
  • 2018-05-02
相关资源
最近更新 更多