Can anyone explain what is the practical use of them? The fact is that I understand the mechanism of their work, but I do not understand why they are needed and where they can be used.

Consider an example:

class Animal { public: Animal():itsAge(1) { cout << "Animal constructor...\n"; } virtual ~Animal() { cout << "Animal destructor...\n"; } virtual void Speak() const { cout << "Animal speak!\n"; } protected: int itsAge; }; class Dog : public Animal { public: Dog() { cout << "Dog constructor...\n"; } virtual ~Dog() { cout << "Dog destructor...\n"; } void Speak() const { cout << "Woof!\n"; } void WagTail() { cout << "Wagging Tail...\n"; } } int main() { Animal *pDog = new Dog; pDog->Speak(); return 0; } 

RESULT:

  • Animal constructor ...
  • Dog constructor ...
  • Woof!

The essence of using virtual functions is that when a method is accessed via a pointer, it will be that option that was declared as virtual in the base class and redefined in the derived class.

But first, using the class pointer Animal * pDog, you still won’t get access to the WagTail () method (waving the tail), since it was not defined in the Animal class. And secondly, using this mechanism will have to pay certain costs associated with the creation of a v-table (each element of which occupies the memory resources).

In addition, I do not understand why pass a pointer to a derived class object when a pointer to a base class object is expected?

Both of the above problems could be solved by declaring the methods in both the base and the derivative not non-virtual , and then write the following:

 Dog *pDog = new Dog; 

instead:

 Animal *pDog = new Dog; 

Where is the "profit"?

  • Well, let's say so - in principle, absolutely everything can be written on a regular assembler or even in machine codes, but this does not mean that all the capabilities of the HLL are not needed? :) - Harry

3 answers 3

Consider the classic example with graphic forms.

You can define a Shape class that contains common methods for all geometric shapes that you intend to use in your application.

This class defines a common interface for all geometric shapes.

And let's say you have a form on which you want to place geometric shapes. The form does not know in advance what geometric shapes it will have to include. It refers to geometric shapes, as some abstract objects that are endowed with some methods that a form can use to bring these shapes to the console.

Since any number of geometric shapes of different types can be added to the form, the question arises, how to store them in the form? If there is no general abstract representation of these figures, then they cannot be stored in a form, since one specific type is needed for objects, so that they can all be stored in any one container and not worry that the figures are actually different. .

This is easy to do if you inherit all the shapes from one class, as in this case, from the Shape class, and in this class you define virtual methods with which the form can work, regardless of what particular object the form deals with.

Below is a simple demo program that implements the ideas described.

There is one Form class that stores all geometric shapes (in this case, objects of the classes LeftTriangle , RightTriangle and Rectangle ) in a standard container std::vector , and which has a display method that allows you to output all forms to the console, delegating to each shape the output process yourself

 // Shape.cpp: определяет точку входа для консольного приложения. // // #include "stdafx.h" #include <iostream> #include <iomanip> #include <vector> #include <memory> struct Point { int x; int y; }; class Shape { protected: Point upper_left; char pixel = '*'; public: explicit Shape(Point p = { 0, 0 }) : upper_left(p) { } virtual ~Shape() = default; char set_pixel(char pixel) { char old_pixel = this->pixel; this->pixel = pixel; return old_pixel; } virtual std::ostream & draw(std::ostream &os = std::cout) const = 0; Point move(int dx = 0, int dy = 0) { Point old_upper_left = this->upper_left; this->upper_left.x += dx; this->upper_left.y += dy; if (this->upper_left.x < 0) this->upper_left.x = 0; if (this->upper_left.y < 0) this->upper_left.y = 0; return old_upper_left; } }; class Triangle : public Shape { protected: unsigned int height; public: explicit Triangle(unsigned int height = 1) : height(height) { } }; class LeftTriangle : public Triangle { public: explicit LeftTriangle(unsigned int height = 1) : Triangle(height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw( upper_left.x ) << std::setfill( ' ' ) << "" << std::setw(i + 2) << std::setfill(pixel) << '\n'; } return os; } }; class RightTriangle : public Triangle { public: explicit RightTriangle( unsigned int height = 1) : Triangle( height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = height; i != 0; i-- ) { os << std::setw(upper_left.x + i - 1 ) << std::setfill( ' ' ) << "" << std::setw( height - i + 2 ) << std::setfill( pixel ) << '\n'; } return os; } }; class Rectangle : public Shape { protected: unsigned int height; unsigned int width; public: explicit Rectangle( unsigned int height = 1, unsigned int width = 1 ) : height(height), width( width ) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw(upper_left.x ) << std::setfill( ' ' ) << "" << std::setw( width + 1 ) << std::setfill(pixel) << '\n'; } return os; } }; class Form { public: Form() = default; void add( Shape * &&shape ) { shapes.push_back(std::unique_ptr<Shape>( shape )); } std::ostream & display(std::ostream &os = std::cout) const { const int Step = 10; int dx = 0; for (auto &p : shapes) { p->move(dx); p->draw(os) << std::endl; dx += Step; } return os; } private: std::vector<std::unique_ptr<Shape>> shapes; }; int main() { Form form; form.add(new RightTriangle(5)); form.add(new LeftTriangle(5)); form.add(new Rectangle(5, 5)); form.display(); return 0; } 

Output of the program to the console

  * ** *** **** ***** * ** *** **** ***** ***** ***** ***** ***** ***** 

Virtual methods define a common interface for all derived classes, allowing them to determine the implementation of this interface themselves. In order to be able to refer to objects of derived classes, as objects of the same type, endowed with common properties, they must be reduced to some general type. This general type can be one of the common base classes of these objects. Thus, polymorphism is achieved, that is, objects that look like objects of the same type have many forms of behavior and representations.

Of course, each derived class can additionally define its data members and methods. But in this case, this is what distinguishes them from objects of other derived classes.

For example, you can say that every woman and every man is a man. But you cannot say, for example, that every person is a woman, or every person is a man. If you consider women and men as people, then you can address them regardless of gender, sending them, as they say in the PLO, various messages. For example, if you are a bus conductor, you can ask for a ticket to be presented. For you, women and men on the bus are passengers, and they should have common properties, such as having a ticket. To do this, you must treat men and women as objects of some general type, in this case, as passengers. Nevertheless, men and women as objects of their individual class are different. For example, women can give birth, but men cannot (unless men are not women who have formally changed their gender according to documents).

    I will not paint the benefits for all sorts of dogs or geometric figures, I will express one trite sounding consideration, but which meant a lot to my understanding at one time.

    When they say that inheritance makes code reuse easier, then what code are we talking about? That the derived class uses code from the base class? By no means. Such things can be done by simple function calls.

    Inheritance makes it possible to reuse the code already written (or even compiled as dynamic libraries) .

    Any f(Base*); used again without any changes, working with code that was not closely written, or maybe not even designed, when this f() was written and compiled - it is simply the code of a virtual function derived from the Base class.

    Yes, it is to a certain extent an analogue of passing in functions of pointers to other functions, but only to a very certain degree. And besides, the question "why do we need the transfer of other functions to functions?"

      As you know, C ++ is a compiled statically typed language, which means the decidability of types during compilation, and so the mechanism of virtual methods extends this possibility for the case when the programmer wants to determine the type of the object at run time , using, for example, dynamic_cast which in the case of a non-polymorphic class, it would not work, but produced a compilation error.

      Where is the "profit"?

      let's say there is such a code

       struct SomeThing { virtual void someGenericOperation() { cout << "base generic operation" } ~SomeThing(); }; struct SomeThingConcrete : SomeThing { void someConcreteOperation() {} void someGenericOperation() { cout << "override generic op" } // override }; SomeThing* p_smth = ...; 

      And let us have such a situation in which we want to check that p_smth indicates the type of SomeThingConcrete so that it is possible to call methods that are specific to SomeThingConcrete - someConcreteOperation() .

      verification is as follows

       SomeThingConcrete* p_concrete = dynamic_cast<SomeThingInterface*>(p_smth); if (p_concrete != NULL) { p_concrete->someConcreteOperation(); } else { // p_concrete does not point to SomeThingInterface } 

      That is, if the downcast to the SomeThing type worked (the p_concrete != NULL condition p_concrete != NULL fulfilled), then our assumption was confirmed and p_smth really points to SomeThingConcrete , otherwise p_smth points to some other class derived from SomeThing .

      In addition, if the base class were not polymorphic, there would be noodles of extra code, for example, if you pass a pointer to base void f(SomeThing*) to the function, then because of the static binding property, you would have to overload the function f for SomeThingConcrete and for each new successor from SomeThing - so that the version of the inherited method that is redefined in a derivative is called.

      Dynamic binding, using the built-in mechanism for invoking virtual methods, would relieve such overloads at all - that is

       void f(SomeThing* p_smth) // f is a single polymorphic function { p_smth->someGenericOperation(); { int main() { SomeThing* p_smth = new SomeThing(); SomeThing* p_smthConcrete = new SomeThingConcrete(); f(p_smth); // base generic operation f(p_smthConcrete); // override generic operation }