Why do we need virtual functions in C++?

前端 未结 26 3104
北恋
北恋 2020-11-21 05:50

I\'m learning C++ and I\'m just getting into virtual functions.

From what I\'ve read (in the book and online), virtual functions are functions in the base class that

相关标签:
26条回答
  • 2020-11-21 06:10

    Here is a merged version of the C++ code for the first two answers.

    #include        <iostream>
    #include        <string>
    
    using   namespace       std;
    
    class   Animal
    {
            public:
    #ifdef  VIRTUAL
                    virtual string  says()  {       return  "??";   }
    #else
                    string  says()  {       return  "??";   }
    #endif
    };
    
    class   Dog:    public Animal
    {
            public:
                    string  says()  {       return  "woof"; }
    };
    
    string  func(Animal *a)
    {
            return  a->says();
    }
    
    int     main()
    {
            Animal  *a = new Animal();
            Dog     *d = new Dog();
            Animal  *ad = d;
    
            cout << "Animal a says\t\t" << a->says() << endl;
            cout << "Dog d says\t\t" << d->says() << endl;
            cout << "Animal dog ad says\t" << ad->says() << endl;
    
            cout << "func(a) :\t\t" <<      func(a) <<      endl;
            cout << "func(d) :\t\t" <<      func(d) <<      endl;
            cout << "func(ad):\t\t" <<      func(ad)<<      endl;
    }
    

    Two different results are:

    Without #define virtual, it binds at compile time. Animal *ad and func(Animal *) all point to the Animal's says() method.

    $ g++ virtual.cpp -o virtual
    $ ./virtual 
    Animal a says       ??
    Dog d says      woof
    Animal dog ad says  ??
    func(a) :       ??
    func(d) :       ??
    func(ad):       ??
    

    With #define virtual, it binds at run time. Dog *d, Animal *ad and func(Animal *) point/refer to the Dog's says() method as Dog is their object type. Unless [Dog's says() "woof"] method is not defined, it will be the one searched first in the class tree, i.e. derived classes may override methods of their base classes [Animal's says()].

    $ g++ virtual.cpp -D VIRTUAL -o virtual
    $ ./virtual 
    Animal a says       ??
    Dog d says      woof
    Animal dog ad says  woof
    func(a) :       ??
    func(d) :       woof
    func(ad):       woof
    

    It is interesting to note that all class attributes (data and methods) in Python are effectively virtual. Since all objects are dynamically created at runtime, there is no type declaration or a need for keyword virtual. Below is Python's version of code:

    class   Animal:
            def     says(self):
                    return  "??"
    
    class   Dog(Animal):
            def     says(self):
                    return  "woof"
    
    def     func(a):
            return  a.says()
    
    if      __name__ == "__main__":
    
            a = Animal()
            d = Dog()
            ad = d  #       dynamic typing by assignment
    
            print("Animal a says\t\t{}".format(a.says()))
            print("Dog d says\t\t{}".format(d.says()))
            print("Animal dog ad says\t{}".format(ad.says()))
    
            print("func(a) :\t\t{}".format(func(a)))
            print("func(d) :\t\t{}".format(func(d)))
            print("func(ad):\t\t{}".format(func(ad)))
    

    The output is:

    Animal a says       ??
    Dog d says      woof
    Animal dog ad says  woof
    func(a) :       ??
    func(d) :       woof
    func(ad):       woof
    

    which is identical to C++'s virtual define. Note that d and ad are two different pointer variables referring/pointing to the same Dog instance. The expression (ad is d) returns True and their values are the same <main.Dog object at 0xb79f72cc>.

    0 讨论(0)
  • 2020-11-21 06:10

    The problem with explanations to virtual functions, is that they don't explain how it is used in practice, and how it helps with maintainability. I've created a virtual function tutorial which people have already found very useful. Plus, it's based on a battlefield premise, which makes it a bit more exciting: https://nrecursions.blogspot.com/2015/06/so-why-do-we-need-virtual-functions.html.

    Consider this battlefield application:

    #include "iostream"
    
    //This class is created by Gun1's company
    class Gun1 {public: void fire() {std::cout<<"gun1 firing now\n";}};
    //This class is created by Gun2's company
    class Gun2 {public: void shoot() {std::cout<<"gun2 shooting now\n";}};
    
    //We create an abstract class to interface with WeaponController
    class WeaponsInterface {
     public:
     virtual void shootTarget() = 0;
    };
    
    //A wrapper class to encapsulate Gun1's shooting function
    class WeaponGun1 : public WeaponsInterface {
     private:
     Gun1* g;
    
     public:
     WeaponGun1(): g(new Gun1()) {}
     ~WeaponGun1() { delete g;}
     virtual void shootTarget() { g->fire(); }
    };
    
    //A wrapper class to encapsulate Gun2's shooting function
    class WeaponGun2 : public WeaponsInterface {
     private:
     Gun2* g;
    
     public:
     WeaponGun2(): g(new Gun2()) {}
     ~WeaponGun2() { delete g;}
     virtual void shootTarget() { g->shoot(); }
    };
    
    class WeaponController {
     private:
     WeaponsInterface* w;
     WeaponGun1* g1;
     WeaponGun2* g2;
     public:
     WeaponController() {g1 = new WeaponGun1(); g2 = new WeaponGun2(); w = g1;}
     ~WeaponController() {delete g1; delete g2;}
     void shootTarget() { w->shootTarget();}
     void changeGunTo(int gunNumber) {//Virtual functions makes it easy to change guns dynamically
       switch(gunNumber) {
         case 1: w = g1; break;
         case 2: w = g2; break;
       }
     }
    };
    
    
    class BattlefieldSoftware {
     private:
     WeaponController* wc;
     public:
     BattlefieldSoftware() : wc(new WeaponController()) {}
     ~BattlefieldSoftware() { delete wc; }
    
     void shootTarget() { wc->shootTarget(); }
     void changeGunTo(int gunNumber) {wc->changeGunTo(gunNumber); }
    };
    
    
    int main() {
     BattlefieldSoftware* bf = new BattlefieldSoftware();
     bf->shootTarget();
     for(int i = 2; i > 0; i--) {
         bf->changeGunTo(i);
         bf->shootTarget();
     }
     delete bf;
    }
    

    I encourage you to first read the post on the blog to get the gist of why the wrapper classes were created.

    As visible in the image, there are various guns/missiles that can be connected to a battlefield software, and commands can be issued to those weapons, to fire or re-calibrate etc. The challenge here is to be able to change/replace the guns/missiles without having to make changes to the blue battlefield software, and to be able to switch between weapons during runtime, without having to make changes in the code and re-compile.

    The code above shows how the problem is solved, and how virtual functions with well-designed wrapper classes can encapsulate functions and help in assigning derived class pointers during runtime. The creation of class WeaponGun1 ensures that you've completely separated the handling of Gun1 into the class. Whatever changes you do to Gun1, you'll only have to make changes in WeaponGun1, and have the confidence that no other class is affected.

    Because of WeaponsInterface class, you can now assign any derived class to the base class pointer WeaponsInterface and because it's functions are virtual, when you call WeaponsInterface's shootTarget, the derived class shootTarget gets invoked.

    Best part is, you can change guns during runtime (w=g1 and w=g2). This is the main advantage of virtual functions and this is why we need virtual functions.

    So no more necessity to comment out code in various places when changing guns. It's now a simple and clean procedure, and adding more gun classes is also easier because we just have to create a new WeaponGun3 or WeaponGun4 class and we can be confident that it won't mess up BattlefieldSoftware's code or WeaponGun1/WeaponGun2's code.

    0 讨论(0)
  • 2020-11-21 06:11

    Here is complete example that illustrates why virtual method is used.

    #include <iostream>
    
    using namespace std;
    
    class Basic
    {
        public:
        virtual void Test1()
        {
            cout << "Test1 from Basic." << endl;
        }
        virtual ~Basic(){};
    };
    class VariantA : public Basic
    {
        public:
        void Test1()
        {
            cout << "Test1 from VariantA." << endl;
        }
    };
    class VariantB : public Basic
    {
        public:
        void Test1()
        {
            cout << "Test1 from VariantB." << endl;
        }
    };
    
    int main()
    {
        Basic *object;
        VariantA *vobjectA = new VariantA();
        VariantB *vobjectB = new VariantB();
    
        object=(Basic *) vobjectA;
        object->Test1();
    
        object=(Basic *) vobjectB;
        object->Test1();
    
        delete vobjectA;
        delete vobjectB;
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-21 06:11

    Are you familiar with function pointers? Virtual functions are a similar idea, except you can easily bind data to virtual functions (as class members). It is not as easy to bind data to function pointers. To me, this is the main conceptual distinction. A lot of other answers here are just saying "because... polymorphism!"

    0 讨论(0)
  • 2020-11-21 06:12

    About efficiency, the virtual functions are slightly less efficient as the early-binding functions.

    "This virtual call mechanism can be made almost as efficient as the "normal function call" mechanism (within 25%). Its space overhead is one pointer in each object of a class with virtual functions plus one vtbl for each such class" [A tour of C++ by Bjarne Stroustrup]

    0 讨论(0)
  • 2020-11-21 06:13

    The virtual keyword forces the compiler to pick the method implementation defined in the object's class rather than in the pointer's class.

    Shape *shape = new Triangle(); 
    cout << shape->getName();
    

    In the above example, Shape::getName will be called by default, unless the getName() is defined as virtual in the Base class Shape. This forces the compiler to look for the getName() implementation in the Triangle class rather than in the Shape class.

    The virtual table is the mechanism in which the compiler keeps track of the various virtual-method implementations of the subclasses. This is also called dynamic dispatch, and there is some overhead associated with it.

    Finally, why is virtual even needed in C++, why not make it the default behavior like in Java?

    1. C++ is based on the principles of "Zero Overhead" and "Pay for what you use". So it doesn't try to perform dynamic dispatch for you, unless you need it.
    2. To provide more control to the interface. By making a function non-virtual, the interface/abstract class can control the behavior in all its implementations.
    0 讨论(0)
提交回复
热议问题