Clever Geek Handbook
📜 ⬆️ ⬇️

Virtual method

Virtual method ( virtual function ) - in object-oriented programming , the class method (function), which can be redefined in the successor classes so that the concrete implementation of the method for calling will be determined at runtime. Thus, the programmer does not need to know the exact type of the object to work with it through virtual methods: it is enough to know that the object belongs to the class or the descendant of the class in which the method is declared. One of the translations of the word virtual from English may be “actual”, which is more appropriate in meaning.

Content

Usage

Virtual methods are one of the most important techniques for implementing polymorphism . They allow you to create common code that can work with objects of the base class as well as with objects of any of its derived classes. In this case, the base class determines the way of working with objects and any of its heirs can provide a concrete implementation of this method.

Definition Method

Some programming languages ​​(for example, C ++ , C # ) require explicitly indicating that this method is virtual. In other languages ​​(for example, Java , Python ), all methods are virtual by default (but only those methods for which it is possible; for example, in Java methods with private access cannot be overridden due to visibility rules).

The base class may not provide implementations of the virtual method, but only declare its existence. Such methods without implementation are called “pure virtual” (translated English. Pure virtual ) or abstract. A class containing at least one such method will also be abstract . An object of this class cannot be created (in some languages ​​it is allowed, but calling an abstract method will lead to an error). The heirs of an abstract class must provide an implementation for all its abstract methods, otherwise they, in turn, will be abstract classes. An abstract class that contains only abstract methods is called an interface .

Implementation

The technique of invoking virtual methods is also called "dynamic binding." This means that the method name used in the program is associated with the input address of a particular method dynamically (during program execution), and not statically (during compilation), since at the time of compilation, in general, it is impossible to determine which of existing method implementations will be called.

In compiled programming languages, dynamic linking is usually performed using the virtual method table , which is created by the compiler for each class that has at least one virtual method. The elements of the table contain pointers to the implementations of virtual methods that correspond to this class (if a new virtual method is added in the descendant class, its address is added to the table, if a new implementation of the virtual method is created in the descendant class - the corresponding field in the table is filled with the address of this implementation) . Thus, for the address of each virtual method in the inheritance tree, there is one fixed offset in the table of virtual methods. Each object has a technical field, which, when creating the object, is initialized with a pointer to a table of virtual methods of its class. To call a virtual method from the object, a pointer to the corresponding table of virtual methods is taken, and from it, at a known fixed offset, a pointer to the implementation of the method used for this class. When using multiple inheritance, the situation is somewhat complicated due to the fact that the table of virtual methods becomes non-linear.

C ++ Virtual Function Example

 
Animal class diagram

An example in C ++ illustrating the difference between virtual functions and non-virtual ones:

Suppose the base class Animal can have a virtual eat method. A subclass (descendant class) Fish (fish) will override the eat() method not so much as the Wolf (wolf) subclass will override it, but you can call eat() on any instance of the class inherited from the Animal class and get the eat() behavior corresponding to this subclass.

This allows the programmer to process a list of objects of the Animal class, calling the eat() method on each object, without thinking about which subclass the current object belongs to (that is, how a particular animal eats).

An interesting detail of virtual functions in C ++ is the default behavior of arguments . When a virtual function is called with an argument by default, the body of the function is taken from the real object, and the values ​​of the arguments are by reference or pointer type.

  class Animal {
 public :
     void / * non-virtual * / move () { 
         std :: cout << "This animal moves in some way" << std :: endl ; 
     }
     virtual void eat () {
         std :: cout << "Animal eat something!"  << std :: endl ; 
     }
     virtual ~ Animal () {} // destructor
 };

 class Wolf : public Animal {
 public :
     void move () { 
         std :: cout << "Wolf walks" << std :: endl ; 
     }
     void eat ( void ) { // the eat method is overridden and also virtual
         std :: cout << "Wolf eats meat!"  << std :: endl ; 
     }
 };

 int main () {
     Animal * zoo [] = { new Wolf (), new Animal ()};
     for ( Animal * a : zoo ) {
         a -> move ();
         a -> eat ();
         delete a ;  // Since the destructor is virtual, for each
                   // the object is called the destructor of its class
     }
     return 0 ;
 }

Conclusion:

  This animal moves in some way
 Wolf eats meat!
 This animal moves in some way
 Animal eat something! 

An example of an analogue of virtual functions in PHP

An analog in PHP can be considered the use of late static binding. [one]

  class Foo {
     public static function baz () {
         return 'water' ;
     }
     public function __construct () {
         echo static :: baz ();  // late static binding
     }
 }

 class Bar extends Foo {
     public static function baz () {
         return 'fire' ;
     }
 }

 new Foo ();  // print 'water'
 new Bar ();  // print 'fire'

Delphi virtual function example

Object Pascal polymorphism used in Delphi. Consider an example:

Declare two classes. Ancestor:

  TAncestor = class
  private
  protected
  public
    {Virtual procedure.} 
    procedure VirtualProcedure ;  virtual; 
    procedure StaticProcedure ;
  end;

and its descendant (Descendant):

  TDescendant = class (TAncestor)
  private
  protected
  public
     {Overlap of the virtual procedure.}
    procedure VirtualProcedure;  override;
    procedure StaticProcedure;
  end;

As you can see in the ancestor class, a virtual function is declared - VirtualProcedure . To take advantage of polymorphism, it must be blocked in the descendant.

The implementation is as follows:

  {TAncestor}
   
  procedure TAncestor.StaticProcedure;
  begin
    ShowMessage ('Ancestor static procedure.');
  end;
   
  procedure TAncestor.VirtualProcedure;
  begin
    ShowMessage ('Ancestor virtual procedure.');
  end;
  {TDescendant}
   
  procedure TDescendant.StaticProcedure;
  begin
    ShowMessage ('Descendant static procedure.');
  end;
   
  procedure TDescendant.VirtualProcedure;
  begin
    ShowMessage ('Descendant override procedure.');
  end;

Let's see how it works:

  procedure TForm2.BitBtn1Click (Sender: TObject);
  var
    MyObject1: TAncestor;
    MyObject2: TAncestor;
  begin
    MyObject1 : = TAncestor .Create;
    MyObject2 : = TDescendant .Create;
    try
      MyObject1.StaticProcedure;
      MyObject1.VirtualProcedure;
      MyObject2.StaticProcedure;
      MyObject2.VirtualProcedure;
    finally
      MyObject1.Free;
      MyObject2.Free;
    end;
  end;

Notice that in the var section, we declared two objects MyObject1 and MyObject2 type TAncestor . And when you create, MyObject1 created as TAncestor , and MyObject2 as TDescendant . Here's what we see when we click on the BitBtn1 button:

  1. Ancestor static procedure.
  2. Ancestor virtual procedure.
  3. Ancestor static procedure.
  4. Descendant override procedure.

For MyObject1 everything is clear, just the specified procedures were called. But for MyObject2 this is not so.

Call MyObject2.StaticProcedure; led to the emergence of "Ancestor static procedure." After all, we declared MyObject2: TAncestor , which is why the StaticProcedure; procedure was called StaticProcedure; TAncestor class.

And here is the call to MyObject2.VirtualProcedure; led to a call to VirtualProcedure; implemented in a descendant ( TDescendant ). This happened because MyObject2 was created not as TAncestor , but as TDescendant : MyObject2 := TDescendant.Create; . And the virtual method VirtualProcedure was overridden.

In Delphi, polymorphism is implemented using the so-called virtual method table (or VMT).

Quite often, virtual methods forget to override using the override keyword. This leads to the closure of the method. In this case, the replacement of methods in VMT will not occur and the required functionality will not be obtained.

This error is monitored by the compiler, which generates a warning.

Example virtual method in C #

Example virtual method in C #. The example uses the base keyword, which provides access to the a() method of the parent (base) class A.

  class program
 {
     static void Main ( string [] args )
     {
         A myObj = new B ();
         Console  ReadKey ();
     }        
 }

 // Base class A
 public class A
 {
     public virtual string a ()
     {
         return "fire" ;
     }
 }

 // Arbitrary class B inheriting class A
 class B : A
 {
     public override string a ()
     {
         return "water" ;
     }

     public B ()
     {
         // Print the result returned by the overridden method
         Console  Out .  WriteLine ( a ());  //water
         // Print the result returned by the parent class method
         Console  Out .  WriteLine ( base.a ());  //the fire
     }
 }

Ancestor Method Call from Overridden Method

It may be necessary to call the ancestor method in an overlapped method.

Declare two classes. Ancestor:

  TAncestor = class
  private
  protected
  public
    {Virtual procedure.} 
    procedure VirtualProcedure ;  virtual; 
  end;

and its descendant (Descendant):

  TDescendant = class (TAncestor)
  private
  protected
  public
     {Overlap of the virtual procedure.}
    procedure VirtualProcedure;  override;
  end;

The call to the ancestor method is implemented using the keyword "inherited"

  procedure TDescendant.VirtualProcedure;
  begin
      inherited;
  end;

It is worth remembering that in Delphi the destructor must be necessarily overlapped - “override” - and contain a call to the ancestor destructor

  TDescendant = class (TAncestor)
  private
  protected
  public
     destructor Destroy;  override;
  end;
  destructor TDescendant.  Destroy
  begin
      inherited;
  end;

In C ++, you do not need to call the constructor and destructor of the ancestor, the destructor must be virtual. Ancestor destructors are called automatically. To call the ancestor method, you must explicitly call the method:

  class Ancestor
 {
 public :
   virtual void function1 () { printf ( "Ancestor :: function1" );  }
 };

 class Descendant : public Ancestor
 {
 public :
   virtual void function1 () {
      printf ( "Descendant :: function1" );
      Ancestor :: function1 ();  // "Ancestor :: function1" will be printed here
   }
 };

To call the constructor of the ancestor, you need to specify the constructor:

  class Descendant : public Ancestor
 {
 public :
   Descendant () : Ancestor () {}
 };


More examples.

First example
  class Ancestor
 {
 public :
   virtual void function1 () { cout << "Ancestor :: function1 ()" << endl ;  }
   void function2 () { cout << "Ancestor :: function2 ()" << endl ;  }
 };

 class Descendant : public Ancestor
 {
 public :
   virtual void function1 () { cout << "Descendant :: function1 ()" << endl ;  }
   void function2 () { cout << "Descendant :: function2 ()" << endl ;  }
 };

 Descendant * pointer = new Descendant ();
 Ancestor * pointer_copy = pointer ;

 pointer -> function1 ();
 pointer -> function2 ();

 pointer_copy -> function1 ();
 pointer_copy -> function2 ();

In this example, the Ancestor class defines two functions, one of them is virtual, the other is not. The Descendant class overrides both functions. However, it would seem that the same call to functions gives different results. The output of the program will give the following:

  Descendant :: function1 ()
     Descendant :: function2 ()
     Descendant :: function1 ()
     Ancestor :: function2 ()

That is, to determine the implementation of a virtual function, information about the type of the object is used and the “correct” implementation is called, regardless of the type of pointer. When a non-virtual function is called, the compiler is guided by the type of the pointer or reference, therefore two different implementations of function2() are called, despite the fact that the same object is used.

It should be noted that in C ++ you can, if necessary, specify a specific implementation of a virtual function, actually calling it non-virtual:

  pointer -> Ancestor :: function1 ();

for our example, Ancestor :: function1 () will output, ignoring the type of the object.

Second example
  class A
 {
 public :
      virtual int function () {
         return 1 ;
      }
     
      int get () {
          return this -> function ();
      }
 };
 
 class B : public A
 {
 public :
      int function () {
         return 2 ;
      }
 };

 #include <iostream> 

 int main () {
    B b ; 
 
    std :: cout << b .  get () << std :: endl ;  // 2
   
    return 0 ;
 }

Despite the fact that there is no get () method in class B , it can be borrowed from class A , and the result of this method will return calculations for B :: function () !

Third example
  #include <iostream> 
 using namespace std ;

 struct IBase
 {
	 virtual void foo ( int n = 1 ) const = 0 ;
	 virtual ~ IBase () = 0 ;
 };

 void IBase :: foo ( int n ) const {
	 cout << n << "foo \ n " ;
 } 

 IBase :: ~ IBase () {
	 cout << "Base destructor \ n " ;
 }

 struct Derived final : IBase
 {
	 virtual void foo ( int n = 2 ) const override final {
		 IBase :: foo ( n );
	 }
 };

 void bar ( const IBase & arg )
 {
	 arg .  foo ();
 }

 int main () {
	 bar ( Derived ());
	 return 0 ;
 }

This example shows an example of creating an IBase interface. Using the interface example, the possibility of creating an abstract class that does not have virtual methods is shown: when declaring a destructor as pure virtual and making its definition outside the body of the class, the ability to create objects of this class is lost, but it remains possible to create descendants of this ancestor.

The output of the program will be as follows: 1 foo \ nBase destructor \ n . As we can see, the default value of the argument came from the type of link, and not from the actual type of the object. As well as the destructor.

The final keyword indicates that the class or method cannot be overridden, and override that the virtual method is explicitly overridden.

See also

  • Object oriented programming
  • Polymorphism (computer science)
  • Abstract class

Notes

  1. ↑ PHP: Late Static Binding - Manual (unspecified) . php.net. Date of treatment November 5, 2016.

Links

  • C ++ FAQ Lite: Virtual Functions in C ++
  • Lecture. Virtual functions and polymorphism / Summaries of the course on C ++ 2010/11, St. Petersburg Department of Mathematical Institute. V.A. Steklova RAS
Source - https://ru.wikipedia.org/w/index.php?title=Virtual_method&oldid=100055668


More articles:

  • Hals, Russell Alan
  • Nascimento, Jefferson
  • Egersky Life Guards Regiment
  • Conradin
  • Ramirez, Oscar
  • Julodimorpha bakewelli
  • Fountain (Alpes-Maritimes)
  • La Gaude
  • Edwardian era
  • Lucius Julius Proculian

All articles

Clever Geek | 2019