Wyjaśnienie wirtualnego / czystego wirtualnego

346

Co to dokładnie znaczy, jeśli funkcja jest zdefiniowana jako wirtualna i czy jest to to samo, co czysta wirtualna?

Justin
źródło

Odpowiedzi:

339

Z funkcji wirtualnej Wikipedii ...

W programowaniu obiektowym, w językach takich jak C ++ i Object Pascal, funkcja wirtualna lub metoda wirtualna to dziedziczna i dająca się zastąpić funkcja lub metoda, dla której ułatwione jest dynamiczne wysyłanie. Ta koncepcja jest ważną częścią polimorfizmu (środowiska wykonawczego) programowania obiektowego (OOP). Krótko mówiąc, funkcja wirtualna określa funkcję docelową do wykonania, ale cel może nie być znany w czasie kompilacji.

W przeciwieństwie do funkcji niebędącej funkcją wirtualną, gdy funkcja wirtualna jest nadpisywana, najbardziej pochodna wersja jest używana na wszystkich poziomach hierarchii klas, a nie tylko na poziomie, na którym została utworzona. Dlatego jeśli jedna metoda klasy bazowej wywołuje metodę wirtualną, zamiast wersji zdefiniowanej w klasie bazowej zostanie użyta wersja zdefiniowana w klasie pochodnej.

Jest to sprzeczne z funkcjami innymi niż wirtualne, które nadal można przesłonić w klasie pochodnej, ale „nowa” wersja będzie używana tylko przez klasę pochodną i poniżej, ale w ogóle nie zmieni funkcjonalności klasy podstawowej.

natomiast..

Funkcja czysto wirtualna lub metoda czysto wirtualna to funkcja wirtualna, która musi zostać zaimplementowana przez klasę pochodną, ​​jeśli klasa pochodna nie jest abstrakcyjna.

Gdy istnieje metoda czysto wirtualna, klasa jest „abstrakcyjna” i nie można jej tworzyć samodzielnie. Zamiast tego należy użyć klasy pochodnej, która implementuje metody czysto wirtualne. Czysta-wirtualna nie jest w ogóle zdefiniowana w klasie podstawowej, więc klasa pochodna musi ją zdefiniować, lub ta klasa pochodna jest również abstrakcyjna i nie można jej utworzyć. Instancja może być utworzona tylko klasa, która nie ma metod abstrakcyjnych.

Wirtualny zapewnia sposób na zastąpienie funkcjonalności klasy podstawowej, a czysty wirtualny tego wymaga .

Diego Dias
źródło
10
Więc ... czy czysty wirtualny jest słowem kluczowym, czy tylko terminem, który jest używany?
Justin,
197
virtual void Function () = 0; to czysty wirtualny. „= 0” oznacza czystość.
Goz
8
Justin, „czysty wirtualny” jest tylko terminem (nie słowem kluczowym, patrz moja odpowiedź poniżej) używanym do oznaczenia „tej funkcji nie można wdrożyć przez klasę podstawową. Jak powiedział Goz, dodając„ = 0 ”na końcu wirtualnego funkcja sprawia, że ​​jest „czysta”
Nick Haddad,
14
Wierzę, że Stroustrup powiedział, że chce dodać puresłowo kluczowe, ale Bell Labs zamierzał wydać główną wersję C ++, a jego menedżer nie pozwoliłby na to na późnym etapie. Dodawanie słów kluczowych to wielka sprawa.
kwark
14
To nie jest dobra odpowiedź. Można zastąpić dowolną metodę, nie tylko wirtualną. Zobacz moją odpowiedź, aby uzyskać więcej informacji.
Asik
212

Chciałbym skomentować definicję wirtualnego Wikipedii, którą kilka osób tutaj powtórzyło. [W momencie pisania tej odpowiedzi] Wikipedia zdefiniowała metodę wirtualną jako metodę, którą można zastąpić w podklasach. [Na szczęście Wikipedia była od tamtej pory edytowana i teraz wyjaśnia to poprawnie.] To jest niepoprawne: każdą metodę, nie tylko wirtualną, można zastąpić w podklasach. Wirtualny daje polimorfizm, czyli możliwość wybrania w czasie wykonywania najbardziej pochodnego zastąpienia metody .

Rozważ następujący kod:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

Jaka jest wydajność tego programu?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Pochodne zastępuje każdą metodę Base: nie tylko wirtualną, ale także niewirtualną.

Widzimy, że gdy masz wskaźnik bazowy na pochodną (bDerived), wywołanie NonVirtual wywołuje implementację klasy Base. Jest to rozwiązywane w czasie kompilacji: kompilator widzi, że bDerived jest Bazą *, że NonVirtual nie jest wirtualny, więc robi to z klasą Base.

Jednak wywołanie Virtual wywołuje implementację klasy Derived. Z powodu słowa kluczowego virtual wybór metody odbywa się w czasie wykonywania , a nie w czasie kompilacji. To, co dzieje się tutaj podczas kompilacji, polega na tym, że kompilator widzi, że jest to Base * i że wywołuje metodę wirtualną, więc wstawia wywołanie do vtable zamiast do klasy Base. Ta tabela vt jest tworzona w czasie wykonywania, stąd rozdzielczość w czasie wykonywania do najbardziej pochodnego przesłonięcia.

Mam nadzieję, że nie było to zbyt mylące. Krótko mówiąc, każdą metodę można zastąpić, ale tylko metody wirtualne dają polimorfizm, to znaczy wybór najbardziej pochodnej zmiany w czasie wykonywania. W praktyce jednak zastąpienie metody innej niż wirtualna jest uważane za złą praktykę i rzadko stosowane, dlatego wiele osób (w tym ktokolwiek napisał ten artykuł w Wikipedii) uważa, że ​​można zastąpić tylko metody wirtualne.

Asik
źródło
6
To, że artykuł w Wikipedii (którego w żaden sposób nie bronię) definiuje metodę wirtualną „jako metodę, która może zostać zastąpiona w podklasach”, nie wyklucza możliwości zadeklarowania innych, nie wirtualnych metod o tej samej nazwie. Jest to znane jako przeciążenie.
26
Definicja jest jednak niepoprawna. Metoda, którą można zastąpić w klasie pochodnej, z definicji nie jest wirtualna; to, czy metodę można zastąpić, nie ma znaczenia dla definicji „wirtualnej”. Ponadto „przeładowanie” zwykle odnosi się do posiadania wielu metod o tej samej nazwie i typie zwrotu, ale różnych argumentach, w tej samej klasie; różni się bardzo od „nadpisywania”, co oznacza dokładnie ten sam podpis, ale w klasie pochodnej. Gdy jest to wykonywane niepolimorficznie (nie-wirtualna baza), często nazywane jest „ukrywaniem”.
Asik,
5
To powinna być zaakceptowana odpowiedź. Ten konkretny artykuł na Wikipedii, który tutaj poświęcę trochę czasu, ponieważ nikt inny w tej kwestii tego nie zrobił , jest kompletnym śmieciem. +1, dobry panie.
josaphatv
2
Teraz to ma sens. Dziękuję, proszę pana, za prawidłowe wyjaśnienie, że każda metoda może zostać zastąpiona przez klasy pochodne, a zmiana polega na tym, jak będzie się zachowywał kompilator w wybieraniu funkcji wywoływanej w różnych sytuacjach.
Doodad
3
Pomocne może być dodanie Derived*wywołań z tymi samymi funkcjami, aby doprowadzić punkt do domu. W przeciwnym razie świetna odpowiedź
Jeff Jones
114

Wirtualne słowo kluczowe daje C ++ jego zdolność do wspierania polimorfizmu. Gdy masz wskaźnik do obiektu jakiejś klasy, takiego jak:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

W tym (głupim) przykładzie funkcja GetNumberOfLegs () zwraca odpowiednią liczbę na podstawie klasy obiektu, do którego została wywołana.

Teraz rozważ funkcję „SomeFunction”. Nie ma znaczenia, jaki typ obiektu zwierzęcego jest do niego przekazywany, o ile pochodzi on od Zwierząt. Kompilator automatycznie zarzuci dowolną klasę pochodzenia zwierzęcego na zwierzę, ponieważ jest to klasa bazowa.

Jeśli to zrobimy:

Duck d;
SomeFunction(&d);

wyświetli „2”. Jeśli to zrobimy:

Horse h;
SomeFunction(&h);

wyświetli „4”. Nie możemy tego zrobić:

Animal a;
SomeFunction(&a);

ponieważ nie można go skompilować, ponieważ funkcja wirtualna GetNumberOfLegs () jest czysta, co oznacza, że ​​musi zostać zaimplementowana przez wyprowadzenie klas (podklas).

Czyste funkcje wirtualne są najczęściej używane do definiowania:

a) klasy abstrakcyjne

Są to klasy podstawowe, z których należy wywodzić, a następnie implementować funkcje czysto wirtualne.

b) interfejsy

Są to „puste” klasy, w których wszystkie funkcje są czysto wirtualne i dlatego musisz wyprowadzić, a następnie zaimplementować wszystkie funkcje.

JBRWilkinson
źródło
W twoim przykładzie nie możesz zrobić nr 4, ponieważ nie podałeś implementacji czystej metody wirtualnej. Nie jest to ściśle związane z tym, że metoda jest czysto wirtualna.
iheanyi
@ iheanyi Nie można zapewnić implementacji metody czysto wirtualnej w klasie bazowej. Dlatego przypadek nr 4 nadal jest błędem.
prasad
32

W klasie C ++ słowo wirtualne jest słowem kluczowym, które to oznacza, metoda może zostać zastąpiona (tj. Zaimplementowana) przez podklasę. Na przykład:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

W takim przypadku podklasa może zastąpić funkcję initShape w celu wykonania specjalistycznej pracy:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

Termin czysto wirtualny odnosi się do funkcji wirtualnych, które muszą zostać zaimplementowane przez podklasę i nie zostały zaimplementowane przez klasę podstawową. Metodę wyznacza się jako czystą wirtualną, używając słowa kluczowego virtual i dodając a = 0 na końcu deklaracji metody.

Więc jeśli chcesz uczynić Shape :: initShape czystym wirtualnym, wykonaj następujące czynności:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

Dodając do klasy czystą wirtualną metodę, uczynisz klasę abstrakcyjną klasą podstawową, co jest bardzo przydatne do oddzielania interfejsów od implementacji.

Nick Haddad
źródło
1
Odnośnie „funkcji wirtualnych, które muszą zostać zaimplementowane przez podklasę” - nie jest to do końca prawdą, ale podklasa jest również abstrakcyjna, jeśli nie są. Klasy abstrakcyjne nie mogą być tworzone. Również „nie może być wdrożone przez klasę podstawową” wydaje się wprowadzać w błąd; Sugerowałbym, że „nie było” byłoby lepsze, ponieważ nie ma ograniczeń w modyfikacjach kodu w celu dodania implementacji w klasie podstawowej.
NVRAM,
2
A „funkcja getName nie może być zaimplementowana przez podklasę” nie jest całkiem poprawna. Podklasy mogą implementować metodę (z tą samą lub inną sygnaturą), ale ta implementacja nie NADMIERUJE metody. Możesz zaimplementować Circle jako podklasę i zaimplementować „std :: string Circle :: getName ()” - wtedy możesz wywołać dowolną metodę dla instancji Circle. Ale jeśli zostanie użyty za pomocą wskaźnika kształtu lub odwołania, kompilator wywołałby Shape :: getName ().
NVRAM,
1
Dobre punkty na obu frontach. Próbowałem trzymać się z dala od omawiania specjalnych przypadków dla tego przykładu, zmodyfikuję odpowiedź, aby była bardziej wybaczająca. Dzięki!
Nick Haddad
@NickHaddad Stary wątek, ale zastanawiasz się, dlaczego wywołałeś zmienną m_name. Co m_znaczy
Tqn
1
@Tqn przy założeniu, że NickHaddad przestrzega konwencji, m_name to konwencja nazewnictwa zwana notacją węgierską. M oznacza członka struktury / klasy, liczbę całkowitą.
Ketcomp
16

„Wirtualny” oznacza, że ​​metoda może być nadpisana w podklasach, ale ma bezpośrednio wywoływalną implementację w klasie podstawowej. „Czysta wirtualna” oznacza, że ​​jest to metoda wirtualna bez implementacji, którą można wywołać bezpośrednio. Taki sposób musi zostać co najmniej raz zastąpiony w hierarchii dziedziczenia - jeśli klasa ma jakieś niezaimplementowane metody wirtualne, obiektów tej klasy nie można zbudować i kompilacja się nie powiedzie.

@quark wskazuje, że metody czysto wirtualne mogą mieć implementację, ale ponieważ metody czysto wirtualne muszą zostać zastąpione, domyślnej implementacji nie można bezpośrednio wywołać. Oto przykład metody czysto wirtualnej z domyślną:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Według komentarzy to, czy kompilacja się nie powiedzie, zależy od kompilatora. Przynajmniej w GCC 4.3.3 nie kompiluje się:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Wynik:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()
John Millikin
źródło
musi zostać zastąpione, jeśli chcesz utworzyć instancję klasy. Jeśli nie utworzysz żadnych instancji, kod dobrze się skompiluje.
Glen,
1
kompilacja nie zawiedzie. Jeśli nie ma implementacji (czystej) metody wirtualnej, nie można utworzyć instancji tej klasy / obiektu. Może nie ŁĄCZYĆ, ale się skompiluje.
Tim
@Glen, @tim: na jakim kompilatorze? Kiedy próbuję skompilować program, który buduje klasę abstrakcyjną, nie kompiluje się.
John Millikin,
Kompilacja @John nie powiedzie się tylko, jeśli spróbujesz utworzyć instancję klasy zawierającej PVF. Możesz oczywiście utworzyć wartości wskaźnika lub wartości referencyjnej dla takich klas.
5
Ponadto, John, poniższe zdanie nie jest całkiem właściwe: „Czysta wirtualna” oznacza, że ​​jest to metoda wirtualna bez implementacji. ” Metody czysto wirtualne mogą mieć implementacje. Ale nie możesz wywoływać ich bezpośrednio: musisz przesłonić i użyć implementacji klasy bazowej z podklasy. Pozwala to na zapewnienie domyślnej części implementacji. Nie jest to jednak powszechna technika.
kwark
9

Jak działa wirtualne słowo kluczowe?

Załóżmy, że człowiek jest klasą podstawową, a Indian pochodzi od człowieka.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Zadeklarowanie do_work () jako wirtualnego oznacza po prostu: które do_work () do wywołania zostaną określone TYLKO w czasie wykonywania.

Załóżmy, że tak

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Jeśli wirtualny nie jest używany, to samo jest określane statycznie lub statycznie powiązane przez kompilator, w zależności od tego, który obiekt wywołuje. Więc jeśli obiekt Man wywołuje do_work (), to man do_work () jest nazywany NAWET ZA POMOCĄ PUNKTÓW DO INDYJSKIEGO OBIEKTU

Uważam, że najczęściej głosowana odpowiedź jest myląca - Każda metoda, niezależnie od tego, czy wirtualna może mieć nadpisaną implementację w klasie pochodnej. W konkretnym odniesieniu do C ++ poprawną różnicą jest powiązanie w czasie wykonywania (gdy używany jest wirtualny) i czas kompilacji (gdy nie jest używany wirtualny, ale metoda jest nadpisana, a wskaźnik bazowy jest wskazywany na obiekcie pochodnym) powiązanie powiązanych funkcji.

Wydaje się, że istnieje inny mylący komentarz, który mówi:

„Justin,„ czysta wirtualna ”to tylko termin (nie słowo kluczowe, patrz moja odpowiedź poniżej) oznaczający„ ta funkcja nie może zostać zaimplementowana przez klasę podstawową ”.

TO JEST ŹLE! Funkcje czysto wirtualne mogą również mieć ciało I MOŻNA BYĆ WDROŻONE! Prawda jest taka, że ​​czystą funkcję wirtualną klasy abstrakcyjnej można nazwać statycznie! Dwaj bardzo dobrzy autorzy to Bjarne Stroustrup i Stan Lippman .... ponieważ napisali ten język.

McMurdo
źródło
2
Niestety, gdy odpowiedź zacznie być oceniana, wszystkie pozostałe zostaną zignorowane. Nawet jeśli mogłyby być lepsze.
LtWorf
3

Funkcja wirtualna jest funkcją składową zadeklarowaną w klasie bazowej i przedefiniowaną przez klasę pochodną. Funkcje wirtualne są hierarchiczne w kolejności dziedziczenia. Gdy klasa pochodna nie przesłania funkcji wirtualnej, używana jest funkcja zdefiniowana w jej klasie bazowej.

Czysta funkcja wirtualna to taka, która nie zawiera definicji względem klasy podstawowej. Nie ma implementacji w klasie bazowej. Każda klasa pochodna musi zastąpić tę funkcję.

rashedcs
źródło
2

Simula, C ++ i C #, które domyślnie używają statycznego wiązania metod, programista może określić, że określone metody powinny używać wiązania dynamicznego, oznaczając je jako wirtualne. Dynamiczne wiązanie metod ma kluczowe znaczenie dla programowania obiektowego.

Programowanie obiektowe wymaga trzech podstawowych pojęć: enkapsulacji, dziedziczenia i dynamicznego wiązania metod.

Hermetyzacja pozwala ukryć szczegóły implementacji abstrakcji za prostym interfejsem.

Dziedziczenie pozwala zdefiniować nową abstrakcję jako rozszerzenie lub udoskonalenie istniejącej abstrakcji, uzyskując automatycznie niektóre lub wszystkie jej cechy.

Dynamiczne wiązanie metod pozwala nowej abstrakcji wyświetlać nowe zachowanie, nawet gdy jest używana w kontekście, który oczekuje starej abstrakcji.

PJT
źródło
1

Metody wirtualne MOGĄ zostać zastąpione przez wyprowadzenie klas, ale wymagają implementacji w klasie bazowej (tej, która zostanie zastąpiona)

Metody czysto wirtualne nie mają implementacji klasy podstawowej. Muszą być zdefiniowane przez klasy pochodne. (Więc technicznie zastąpienie nie jest właściwym terminem, ponieważ nie ma nic do zastąpienia).

Wirtualny odpowiada domyślnemu zachowaniu Java, gdy klasa pochodna zastępuje metodę klasy podstawowej.

Metody Pure Virtual odpowiadają zachowaniu metod abstrakcyjnych w klasach abstrakcyjnych. A klasa, która zawiera wyłącznie metody wirtualne i stałe, byłaby cpp-wisiorek interfejsu.

johannes_lalala
źródło
0

Czysta funkcja wirtualna

wypróbuj ten kod

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

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

W klasie anotherClass usuń funkcję sayHellow i uruchom kod. dostaniesz błąd! Ponieważ gdy klasa zawiera czystą funkcję wirtualną, z tej klasy nie można utworzyć żadnego obiektu i jest ona dziedziczona, to klasa pochodna musi zaimplementować tę funkcję.

Funkcja wirtualna

wypróbuj inny kod

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

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

W tym przypadku funkcja sayHellow jest oznaczona jako wirtualna w klasie bazowej. Mówi kompilator, który próbuje wyszukać funkcję w klasie pochodnej i implementuje funkcję. Jeśli nie zostanie znaleziony, uruchom funkcję podstawową.

Tunvir Rahman Tusher
źródło
Haha, zajęło mi długie 30 sekund, aby zrozumieć, co tu jest nie tak ... Cześć :)
hans
0

„Funkcja wirtualna lub metoda wirtualna to funkcja lub metoda, której zachowanie można zastąpić w klasie dziedziczącej funkcją o tej samej sygnaturze” - Wikipedia

To nie jest dobre wytłumaczenie funkcji wirtualnych. Ponieważ nawet jeśli członek nie jest wirtualny, dziedziczenie klas może go zastąpić. Możesz spróbować to zobaczyć sam.

Różnica pojawia się, gdy funkcja przyjmuje klasę podstawową jako parametr. Gdy podajesz klasę dziedziczącą jako dane wejściowe, funkcja ta wykorzystuje implementację klasy podstawowej funkcji przesłonięcia. Jeśli jednak ta funkcja jest wirtualna, używa tej, która jest zaimplementowana w klasie pochodnej.

mogą
źródło
0
  • Funkcje wirtualne muszą mieć definicję w klasie bazowej, a także w klasie pochodnej, ale nie są konieczne, na przykład funkcja ToString () lub toString () jest funkcją wirtualną, dzięki czemu można zapewnić własną implementację, zastępując ją w klasach zdefiniowanych przez użytkownika.

  • Funkcje wirtualne są deklarowane i definiowane w normalnej klasie.

  • Czysta funkcja wirtualna musi być zadeklarowana z końcem „= 0” i może być zadeklarowana tylko w klasie abstrakcyjnej.

  • Klasa abstrakcyjna posiadająca wyłącznie funkcje wirtualne nie może mieć definicji tych funkcji czysto wirtualnych, więc oznacza to, że implementacja musi być zapewniona w klasach pochodzących z tej klasy abstrakcyjnej.

Sohail xIN3N
źródło
Ta sama uwaga co do @rashedcs: Rzeczywiście czysta funkcja wirtualna może mieć swoją definicję ...
Jarek C