Полиморфизъм и виртуално в C++

Объркан съм относно полиморфизма в C++. Изучавам го сам и разбрах основните му характеристики. Но не разбирам защо е полезно. Преди да изучавам полиморфизма (относно oop), изучавах наследяването (това е полезно, защото можете да използвате метод в суперкласа и подкласа, пишейки само веднъж). Сега съм заседнал с полиморфизма и ключовата дума virtual. Не разбирам защо е полезно. Вижте кода по-долу (това е упражнение за института C++ (ще получа сертификат)). Защо мога да декларирам като "виртуални" само функции? Добавям в кода променливите n1, n2, n3 (като публични), защо нямам достъп до тях? Изобщо не разбирам полиморфизма, прочетох купища публикации за полиморфизъм в StackOverflow, но сякаш разбирам полиморфизма на 50%. Забелязах, че полиморфизмът е по-малко труден за разбиране в Python, тъй като Python няма типове данни, но искам да го разбера и в C++ и възможните му употреби.

#include <iostream>
using namespace std;
class Pet {
    protected:
    string Name;
    public:
    Pet(string n) { Name = n; }
    virtual void MakeSound(void) { cout << Name << " the Pet says: Shh! Shh!"  << endl; }
    int n1;
};
class Cat : public Pet {
    public:
    Cat(string n) : Pet(n) { }
    void MakeSound(void) { cout << Name << " the Cat says: Meow! Meow!" <<       endl; }
    int n2;
};
class Dog : public Pet {
    public:
    Dog(string n) : Pet(n) { }
    void MakeSound(void) { cout << Name << " the Dog says: Woof! Woof!" << endl; }
    int n3;
};
int main(void) {
    Pet* a_pet1, * a_pet2;
    Cat* a_cat;
    Dog* a_dog;

    a_pet1 = a_cat = new Cat("Kitty");
    a_pet2 = a_dog = new Dog("Doggie");

    a_pet1->MakeSound();
    a_cat->MakeSound();
    static_cast<Pet*>(a_cat)->MakeSound();
    a_pet2->MakeSound();
    a_dog->MakeSound();
    static_cast<Pet*>(a_dog)->MakeSound();
}

person Riccardo    schedule 02.08.2019    source източник
comment
Сега напишете функция, която взема произволен любимец и извикайте неговата MakeSound функция. Да приемем, че имате 30 различни типа Pet, а не само 2. Може би частта, която пропускате, е в писането на функции, които ще приемат указател или препратка към базов клас.   -  person PaulMcKenzie    schedule 02.08.2019
comment
Възможен дубликат на Защо имаме нужда от виртуални функции в C++? Приетият отговор е доста задълбочен с пример за защо virtual функциите са полезни.   -  person Fureeish    schedule 02.08.2019
comment
Къде е вашият опит за достъп до n1, n2 и n3? Вашата проза предполага, че въпросът ви може да е фокусиран върху тези три, но кодът ви най-вече ги игнорира.   -  person JaMiT    schedule 02.08.2019
comment
Добре, благодаря @Fureeish. Просто виждам публикацията. Да, разбрах го, но наистина не знам как да го използвам в реален контекст, ако го използвам в малка програма, е добре, но други...   -  person Riccardo    schedule 03.08.2019
comment
Помислете за улавяне на const std::exception& и извеждане на неговата what() върната стойност. С функциите virtual не е нужно да знаете точния тип на изключението, за да получите значима информация и резултати.   -  person Fureeish    schedule 03.08.2019
comment
Така че съм нов във всичко това и просто имам въпрос относно кода. int main(void) същото ли е като int main()?   -  person    schedule 03.08.2019


Отговори (3)


Може би един пример може да помогне. Помислете за различен main(), като този:

int main()
{
    std::vector<std::unique_ptr<Pet>> menagerie;
    menagerie.push_back(std::make_unique<Dog>("Fido"));
    menagerie.push_back(std::make_unique<Cat>("Morris"));
    menagerie.push_back(std::make_unique<Cat>("Garfield"));
    menagerie.push_back(std::make_unique<Dog>("Rover"));

    for (auto&& pet : menagerie)
    {
        pet->MakeSound();
    }
}

Тук имаме куп домашни любимци. Можем да се справим с всички тях по един и същи начин, но те издават различни звуци. Извикването на MakeSound на всеки прави правилното нещо за този конкретен вид домашен любимец. Този вид случай на употреба е много често срещан.

Fido the Dog says: Woof! Woof!
Morris the Cat says: Meow! Meow!
Garfield the Cat says: Meow! Meow!
Rover the Dog says: Woof! Woof!

Сега опитайте да премахнете ключовата дума virtual и всички ще кажат „Шшт!

person Fred Larson    schedule 02.08.2019

Прав си, не е лесно да се разбере как полиморфизмът е полезен и какво прави, когато го научиш. Често срещаните примери като посочените от вас всъщност не помагат, което просто демонстрира концепцията, но липсва реален контекст.

Разбирането и възможността за използване на полиморфизъм е доста напреднала тема в програмирането. Използва се, когато се следва наистина обектно ориентирано програмиране като SOLID и шаблони за проектиране.

Добър пример от реалния свят за полиморфизъм в действие е шаблонът за проектиране на итератор. Вие дефинирате базов клас, да речем за списък с метод като next(), след което можете да имате различни производни класове (за различни видове списъци), всички заменящи този метод и те го прилагат, така че да можете да итерирате този списък съответно.

Както може би виждате, става сложно, така че не мога да обясня всичко тук, но получавате представа и някои насоки.

person zar    schedule 02.08.2019
comment
но липсва реален контекст да! Вероятно не съм го разбрал, но не знам къде и кога да го използвам. И за съжаление не знам как да придобия това умение. Какво мога да направя според вас? - person Riccardo; 03.08.2019
comment
@Riccardo Дадох ти насоки, потърси модел на дизайн на итератор и виж полимпорфизъм в него. Друг модел на проектиране, който използва силно полиморфизма, е моделът на проектиране на състояние, но почти всички модели на проектиране го използват. - person zar; 05.08.2019

Ключовата идея на полиморфизма е да има един метод. Този метод ще има различни реализации и конкретна реализация се извиква въз основа на определени ситуации.

Нека разгледаме този пример:

#include <iostream>
using namespace std;
class Polygon{

 protected:
  int numVertices;
  float *xCoord, *yCoord;

 public:
  void set(){
   cout<<"From Polygon"<< endl;
  }

};
class Rectangle : public Polygon{
public:

 void set(){
   cout<<"From Rectangle"<< endl;
}

class Triangle : public Polygon{
public:

 void set(){
   cout<<"From Triangle"<< endl;

 }
};
int main(){

 Polygon *poly;
 Rectangle rec;
 Triangle tri;

 poly = &rec;
 poly->set();
 poly = &tri;
 poly->set();
}

Когато стартирате този код, изходът ви е следният:

From Polygon
From Polygon

Нека добавим virtual към set() в базов клас (Polygon). Ето какво получавате:

From Rectangle
From Triangle

Ако сме създали виртуална функция в базовия клас (Polygon) и тя се отменя в производния клас (В този случай Triangle и Rectangle), тогава не се нуждаем от ключова дума virtual в производния клас, функциите автоматично се считат за виртуални функции в производния клас.

Идеята е set() да извика версията на базовия клас на метода, ако set() не е виртуален дори ако poly е насочен към Rect. От друга страна, set(), който е виртуален, той ще извика действителния метод от производния клас. (В този случай rect->set() ще отпечата „От правоъгълник“).

Това означава, че в ситуации, в които не знам конкретния тип обект, мога да използвам virtual и polymorphism и той ще използва правилния метод по време на повикване.

Надявам се това да помогне!

person abhivemp    schedule 03.08.2019