【问题标题】:Why use virtual functions? [duplicate]为什么要使用虚函数? [复制]
【发布时间】:2012-02-08 02:37:19
【问题描述】:

可能重复:
Can someone explain C++ Virtual Methods?

我有一个关于 C++ 虚函数的问题。

我们为什么以及何时使用虚函数?谁能给我实时实现或使用虚拟功能?

【问题讨论】:

标签: c++ virtual-functions


【解决方案1】:

当您想要覆盖派生类的某种行为(读取方法)而不是为基类实现的行为并且您希望在运行时通过指向基类的指针这样做时,您可以使用虚函数。

典型的例子是当你有一个名为Shape 的基类和派生自它的具体形状(类)时。每个具体类都覆盖(实现一个虚拟方法)称为Draw()

类层次结构如下:

下面的sn -p 展示了例子的用法;它创建了一个Shape 类指针数组,其中每个指针指向一个不同的派生类对象。在运行时,调用 Draw() 方法会导致调用该派生类覆盖的方法,并绘制(或渲染)特定的 Shape

Shape *basep[] = { &line_obj, &tri_obj,
                   &rect_obj, &cir_obj};
for (i = 0; i < NO_PICTURES; i++)
    basep[i] -> Draw ();

上面的程序只是使用指向基类的指针来存储派生类对象的地址。这提供了一种松散的耦合,因为如果随时添加新的具体派生类shape,则程序不必发生巨大变化。原因是实际使用(依赖)具体Shape 类型的代码段最少。

以上是著名的SOLID设计原则Open Closed Principle的一个很好的例子。

【讨论】:

  • 如果我们不使用virtual 而只是在LineTriangle 等子类中重新定义它们会怎样。那里会有什么不同?
  • 如果函数在基类中不是虚函数,则无法通过基类指针访问派生类版本。
  • 抱歉,这里还是有点糊涂。说虚函数适用于在运行时之前不知道需要哪个版本的函数时是否正确,但是在那之前的某个时间您还需要基类的实例化?因为似乎我仍然可以在没有虚拟函数的情况下通过创建子类并在我知道后实例化正确的类直到运行时才知道我需要什么。所以看起来你还需要一个基类的实例化之前的某个时间很关键,对吧?又名:通过基指针调用派生函数
【解决方案2】:

当您需要以相同的方式处理不同的对象时,您可以使用虚函数。它被称为多态性。假设你有一些基类——比如经典的 Shape:

    class Shape
    {
        public:
           virtual void draw() = 0;
           virtual ~Shape() {}
    };

    class Rectange: public Shape
    {
        public:
            void draw() { // draw rectangle here } 
    };


    class Circle: public Shape
    {
        public:
           void draw() { // draw circle here }
    };

现在你可以拥有不同形状的向量:

    vector<Shape*> shapes;
    shapes.push_back(new Rectangle());
    shapes.push_back(new Circle());

你可以像这样画出所有的形状:

    for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++)
    {
          (*i)->draw();
    }

通过这种方式,您可以使用一种虚拟方法 - draw() 绘制不同的形状。根据指针后面的对象类型的运行时信息选择正确的方法版本。

通知 当您使用虚函数时,您可以将它们声明为纯虚函数(就像在类 Shape 中,只需在方法 proto 之后放置“= 0”)。在这种情况下,您将无法使用纯虚函数创建对象实例,它将被称为抽象类。

还要注意析构函数之前的“虚拟”。如果您计划通过指向其基类的指针来处理对象,您应该将析构函数声明为虚拟的,因此当您为基类指针调用“delete”时,将调用所有析构函数链并且不会出现内存泄漏。

【讨论】:

    【解决方案3】:

    想想动物类,从它派生的有猫、狗和牛。动物类有一个

    virtual void SaySomething()
    {
        cout << "Something";
    }
    

    功能。

    Animal *a;
    a = new Dog();
    a->SaySomething();
    

    狗应该说“Bark”,猫应该说“Meow”,而不是打印“Something”。在此示例中,您看到 a 是 Dog,但有时您有一个动物指针,但不知道它是哪种动物。你不想知道它是哪种动物,你只是想让动物说点什么。所以你只要调用虚函数,猫会说“喵”,狗会说“吠”。

    当然,SaySomething 函数应该是纯虚函数以避免可能的错误。

    【讨论】:

    • 谢谢你的回答很满意....
    • 我不太理解你的最后一句话“SaySomething 函数应该是纯虚拟的,以避免可能的错误。”你能举个例子吗?
    • 抱歉回复晚了。考虑到这一点,开发人员创建了一个继承我们的动物类的新类(例如,Fox)并忘记覆盖 SaySomething 方法。如果你使用我提供的虚拟方法,Fox 实例会说“Something”,这是不对的。如果我们将 SaySomething 声明为纯虚拟,我们将无法实例化 Fox,包含 new Fox(...) 的代码将引发错误。这样,创建Fox 类的开发人员将被通知他/她在编译时的错误。编译时错误很好,因为它们不会浪费时间:)
    • 但是在不支持纯虚方法的语言中,“必须实现”的方法应该抛出异常。 Java 开发人员通常会这样做。然而这里的主要问题是,狐狸说什么:P
    【解决方案4】:

    你会使用虚函数来实现“多态性”,特别是当你有一个对象时,不知道实际的底层类型是什么,但知道你想对它执行什么操作,以及它的实现(它是如何做到的)因您实际拥有的类型而异。

    基本上就是通常所说的“Liskov 替换原则”,以 Barbara Liskov 的名字命名,她在 1983 年左右谈到了这一点。

    在您需要使用动态运行时决策的地方,在调用函数的代码被调用时,您不知道现在或将来可能通过它的类型,这是一个很好的模型。

    但这不是唯一的方法。有各种各样的“回调”可以获取“blob”数据,并且您可能拥有依赖于传入数据中的标头块的回调表,例如消息处理器。为此,不需要使用虚函数,实际上您可能会使用一种仅使用一个条目(例如,只有一个虚函数的类)来实现 v-table 的方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2013-10-26
      • 2015-09-22
      • 2011-06-16
      • 1970-01-01
      • 2012-12-16
      • 1970-01-01
      相关资源
      最近更新 更多