vínculo Por que precisamos de funções virtuais em C++?




funções virtuais são resolvidas usando-se vínculo dinâmico (19)

Aqui está como eu entendi não apenas quais funções virtual são, mas porque elas são necessárias:

Vamos dizer que você tem essas duas classes:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Na sua função principal:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

Até aí tudo bem, certo? Animais comem alimentos genéricos, gatos comem ratos, tudo sem virtual .

Vamos mudar um pouco agora para que eat() seja chamado através de uma função intermediária (uma função trivial apenas para este exemplo):

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Agora nossa principal função é:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Uh oh ... nós passamos um gato em func() , mas ele não vai comer ratos. Você deve sobrecarregar func() então é preciso um Cat* ? Se você tiver que derivar mais animais do Animal, todos precisarão do seu próprio func() .

A solução é tornar eat() da classe Animal uma função virtual:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

A Principal:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Feito.

Estou aprendendo C ++ e estou entrando em funções virtuais.

Pelo que eu li (no livro e online), funções virtuais são funções na classe base que você pode substituir em classes derivadas.

Porém, no início do livro, ao aprender sobre herança básica, eu era capaz de substituir funções básicas em classes derivadas sem usar virtual .

Então, o que estou perdendo aqui? Eu sei que há mais funções virtuais, e parece ser importante, então eu quero ser claro sobre o que é exatamente. Eu simplesmente não consigo encontrar uma resposta online.


Tenho minha resposta em forma de conversa para ler melhor:

Por que precisamos de funções virtuais?

Por causa do polimorfismo.

O que é polimorfismo?

O fato de que um ponteiro base também pode apontar para objetos de tipo derivados.

Como essa definição de polimorfismo leva à necessidade de funções virtuais?

Bem, através da ligação inicial .

O que é ligação inicial?

Ligação inicial (ligação em tempo de compilação) em C ++ significa que uma chamada de função é corrigida antes de o programa ser executado.

Assim...?

Portanto, se você usar um tipo base como o parâmetro de uma função, o compilador reconhecerá apenas a interface base e, se você chamar essa função com argumentos de classes derivadas, ela será cortada, o que não é o que você deseja que aconteça.

Se não é o que queremos que aconteça, por que isso é permitido?

Porque precisamos de polimorfismo!

Qual é o benefício do polimorfismo então?

Você pode usar um ponteiro de tipo de base como o parâmetro de uma única função e, em seguida, no tempo de execução do seu programa, você pode acessar cada uma das interfaces de tipo derivadas (por exemplo, suas funções de membro) sem nenhum problema, usando dereferenciamento dessa única ponteiro base.

Eu ainda não sei quais funções virtuais são boas para ...! E esta foi a minha primeira pergunta!

bem, isso é porque você fez sua pergunta cedo demais!

Por que precisamos de funções virtuais?

Suponha que você chamou uma função com um ponteiro base, que tinha o endereço de um objeto de uma das classes derivadas. Como já falamos sobre isso, no tempo de execução, esse ponteiro é desreferenciado, até agora tudo bem, porém, esperamos que um método (== uma função de membro) "de nossa classe derivada" seja executado! No entanto, um mesmo método (um que tenha o mesmo cabeçalho) já está definido na classe base, então por que seu programa deve se preocupar em escolher o outro método? Em outras palavras, como você pode dizer esse cenário do que costumávamos ver normalmente acontecer antes?

A resposta breve é ​​"uma função de membro virtual na base", e uma resposta um pouco mais longa é que "nesta etapa, se o programa vê uma função virtual na classe base, ela sabe (percebe) que você está tentando usar polimorfismo "e assim vai para classes derivadas (usando v-table , uma forma de ligação tardia) para descobrir que outro método com o mesmo cabeçalho, mas com -esperadamente- uma implementação diferente.

Por que uma implementação diferente?

Você cabeça de junta! Vá ler um bom livro !

OK, espera, espera, por que alguém se incomodaria em usar ponteiros de base, quando poderia simplesmente usar ponteiros de tipo derivados? Você é o juiz, toda essa dor de cabeça vale a pena? Veja estes dois trechos:

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

OK, embora eu ache que 1 ainda seja melhor que 2 , você poderia escrever 1 assim:

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

e além disso, você deve estar ciente de que isso é apenas um uso artificial de todas as coisas que eu expliquei até agora. Em vez disso, suponha, por exemplo, uma situação em que você teve uma função em seu programa que usou os métodos de cada uma das classes derivadas, respectivamente (getMonthBenefit ()):

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

Agora, tente reescrever isso, sem dores de cabeça!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

E, na verdade, isso pode ser um exemplo inventado também!


Você tem que distinguir entre substituição e sobrecarga. Sem a palavra-chave virtual você só sobrecarrega um método de uma classe base. Isso significa nada além de se esconder. Digamos que você tenha uma classe base Base e uma classe derivada Specialized que implementem void foo() . Agora você tem um ponteiro para Base apontando para uma instância de Specialized . Quando você chama foo() nele você pode observar a diferença que o virtual faz: Se o método é virtual, a implementação do Specialized será usada, se estiver faltando, a versão do Base será escolhida. É uma prática recomendada nunca sobrecarregar métodos de uma classe base. Tornar um método não virtual é a maneira de seu autor dizer que sua extensão em subclasses não é intencional.


Por que precisamos de métodos virtuais em C ++?

Resposta rápida:

  1. Ele nos fornece um dos "ingredientes" necessários 1 para programação orientada a objetos .

Em Bjarne Stroustrup C ++ Programação: Princípios e Prática, (14.3):

A função virtual fornece a capacidade de definir uma função em uma classe base e ter uma função do mesmo nome e tipo em uma classe derivada chamada quando um usuário chama a função de classe base. Isso é geralmente chamado de polimorfismo de tempo de execução , expedição dinâmica ou expedição em tempo de execução porque a função chamada é determinada em tempo de execução com base no tipo de objeto usado.

  1. É a implementação mais rápida e eficiente se você precisar de uma chamada de função virtual 2 .

Para lidar com uma chamada virtual, é necessário um ou mais dados relacionados ao objeto derivado 3 . A maneira que geralmente é feita é adicionar o endereço da tabela de funções. Essa tabela é geralmente chamada de tabela virtual ou tabela de função virtual e seu endereço é geralmente chamado de ponteiro virtual . Cada função virtual recebe um slot na tabela virtual. Dependendo do tipo de objeto do chamador (derivado), a função virtual, por sua vez, chama a respectiva substituição.

1.O uso de herança, polimorfismo de tempo de execução e encapsulamento é a definição mais comum de programação orientada a objetos .

2. Você não pode codificar funcionalidade para ser mais rápido ou usar menos memória usando outros recursos de idioma para selecionar entre alternativas em tempo de execução. Bjarne Stroustrup C ++ Programação: Princípios e Práticas (14.3.1) .

3. Algo para dizer qual função é realmente invocada quando chamamos a classe base que contém a função virtual.


Quando você tem uma função na classe base, você pode Redefine ou Override la na classe derivada.

Redefinindo um método : Uma nova implementação para o método da classe base é dada na classe derivada. Não facilita a Dynamic binding .

Substituindo um método : Redefining um virtual method da classe base na classe derivada. O método virtual facilita a vinculação dinâmica .

Então quando você disse:

Mas no início do livro, ao aprender sobre herança básica, eu era capaz de substituir os métodos base em classes derivadas sem usar 'virtual'.

você não estava sobrescrevendo, pois o método na classe base não era virtual, e sim você estava redefinindo


Por que precisamos de funções virtuais?

As funções virtuais evitam problemas de typecasting desnecessários, e alguns de nós podem debater isso por que precisamos de funções virtuais quando podemos usar o ponteiro de classe derivada para chamar a função específica na classe derivada! A resposta é - anula toda a idéia de herança no sistema grande desenvolvimento, onde ter objeto de classe base de ponteiro único é muito desejado.

Vamos comparar abaixo dois programas simples para entender a importância das funções virtuais:

Programa sem funções virtuais:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

SAÍDA:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Programa com função virtual:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

SAÍDA:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

Ao analisar de perto ambos os resultados, pode-se entender a importância das funções virtuais.


Aqui está uma versão mesclada do código C ++ para as duas primeiras respostas.

#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;
}

Dois resultados diferentes são:

Sem o #define virtual , ele é ligado em tempo de compilação. Animal * ad e func (Animal *) apontam para o método says () do Animal.

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

Com o #define virtual , ele é ligado no tempo de execução. Dog * d, Animal * ad e func (Animal *) point / referem-se ao método says () do Dog como Dog é o tipo de objeto deles. A menos que o método [dog says () "woof"] não esteja definido, ele será o primeiro pesquisado na árvore de classes, isto é, as classes derivadas podem substituir métodos de suas classes base [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

É interessante notar que todos os atributos de classe (dados e métodos) em Python são efetivamente virtuais . Como todos os objetos são criados dinamicamente no tempo de execução, não há declaração de tipo ou a necessidade de palavra-chave virtual. Abaixo está a versão do código do Python:

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)))

A saída é:

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

que é idêntico ao virtual do C ++ define. Observe que d e ad são duas variáveis ​​de ponteiro diferentes que se referem / apontam para a mesma instância de Dog. A expressão (ad é d) retorna True e seus valores são os mesmos < main .Dog object at 0xb79f72cc>.


Se a classe base for Base e uma classe derivada for Der , você poderá ter um ponteiro Base *p que realmente aponta para uma instância de Der . Quando você chama p->foo(); , se foo não é virtual, a versão de Base dele é executada, ignorando o fato de que p realmente aponta para um Der . Se foo é virtual, p->foo() executa a substituição de foo "mais completa", levando em consideração a classe real do item apontado. Portanto, a diferença entre virtual e não-virtual é realmente crucial: o primeiro permite o polymorphism tempo de execução, o conceito central de programação OO, enquanto o segundo não.


Você precisa de métodos virtuais para segurança , simplicidade e concisão .

Isso é o que os métodos virtuais fazem: eles diminuem com segurança, com código aparentemente simples e conciso, evitando os lançamentos manuais inseguros no código mais complexo e detalhado que você teria de outra forma.

Método não virtual ⇒ ligação estática

O código a seguir é intencionalmente "incorreto". Ele não declara o método do value como virtual e, portanto, produz um resultado "errado" não intencional, a saber 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Na linha comentada como "ruim" o método Expression::value é chamado, porque o tipo conhecido estaticamente (o tipo conhecido em tempo de compilação) é Expression , e o método value não é virtual.

Método virtual ⇒ ligação dinâmica.

Declarar o value como virtual no tipo de Expression estaticamente conhecido garante que cada chamada verifique o tipo real de objeto que é e chame a implementação relevante de value para esse tipo dinâmico :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Aqui a saída é 6.86 como deveria ser, já que o método virtual é chamado virtualmente . Isso também é chamado de ligação dinâmica das chamadas. Uma pequena verificação é executada, sendo encontrado o tipo dinâmico real de objeto e a implementação do método relevante para esse tipo dinâmico.

A implementação relevante é aquela na classe mais específica (mais derivada).

Observe que as implementações de método em classes derivadas aqui não estão marcadas como virtual , mas, em vez disso, são marcadas como override . Eles podem ser marcados como virtual mas são automaticamente virtuais. A palavra-chave override assegura que, se não houver tal método virtual em alguma classe base, você receberá um erro (que é desejável).

A fealdade de fazer isso sem métodos virtuais

Sem o virtual seria necessário implementar alguma versão Do It Yourself da vinculação dinâmica. É isso que geralmente envolve downcasting manual inseguro, complexidade e verbosidade.

Para o caso de uma única função, como aqui, basta armazenar um ponteiro de função no objeto e chamar através desse ponteiro de função, mas mesmo assim envolve alguns downcasts inseguros, complexidade e verbosidade, a saber:

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

Uma maneira positiva de olhar para isto é, se você encontrar downcasting inseguro, complexidade e verbosidade como acima, então muitas vezes um método virtual ou métodos podem realmente ajudar.


Sobre a eficiência, as funções virtuais são um pouco menos eficientes como as funções de ligação inicial.

"Este mecanismo de chamada virtual pode ser feito quase tão eficiente quanto o mecanismo de" chamada de função normal "(dentro de 25%). Sua sobrecarga de espaço é um ponteiro em cada objeto de uma classe com funções virtuais mais um vtbl para cada classe . tour de C ++ por Bjarne Stroustrup]


Funções virtuais são usadas para suportar o polimorfismo de tempo de execução .

Ou seja, a palavra-chave virtual informa ao compilador para não tomar a decisão (de ligação de função) em tempo de compilação, e sim adiá-la para o tempo de execução " .

  • Você pode tornar uma função virtual precedendo a palavra-chave virtual em sua declaração de classe base. Por exemplo,

     class Base
     {
        virtual void func();
     }
  • Quando uma classe base tem uma função de membro virtual, qualquer classe que herda da classe base pode redefinir a função exatamente com o mesmo protótipo, ou seja, somente a funcionalidade pode ser redefinida, não a interface da função.

     class Derive : public Base
     {
        void func();
     }
  • Um ponteiro de classe base pode ser usado para apontar para o objeto de classe Base, bem como para um objeto de classe Derivado.

  • Quando a função virtual é chamada usando um ponteiro de classe base, o compilador decide em tempo de execução qual versão da função - ou seja, a versão de classe base ou a versão de classe derivada substituída - deve ser chamada. Isso é chamado de polimorfismo de tempo de execução .

Você precisa de pelo menos 1 nível de herança e um downcast para demonstrá-lo. Aqui está um exemplo muito simples:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}

Eu acho que você está se referindo ao fato uma vez que um método é declarado virtual, você não precisa usar a palavra-chave 'virtual' em substituições.

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

Se você não usar 'virtual' na declaração foo do Base, então o foo do Derived estaria apenas seguindo isso.


Isso ajuda se você conhece os mecanismos subjacentes. C ++ formaliza algumas técnicas de codificação usadas por programadores C, "classes" substituídas usando "sobreposições" - structs com seções de cabeçalho comuns seriam usadas para manipular objetos de diferentes tipos, mas com alguns dados ou operações comuns. Normalmente, a estrutura base da sobreposição (a parte comum) possui um ponteiro para uma tabela de funções que aponta para um conjunto diferente de rotinas para cada tipo de objeto. C ++ faz a mesma coisa, mas esconde os mecanismos, ou seja, o C ++ ptr->func(...) onde func é virtual como C seria (*ptr->func_table[func_num])(ptr,...) , onde o que muda entre classes derivadas é o conteúdo func_table. [Um método não virtual ptr-> func () traduz apenas para mangled_func (ptr, ..).]

O resultado disso é que você só precisa entender a classe base para chamar os métodos de uma classe derivada, isto é, se uma rotina entende a classe A, você pode passar para ela um ponteiro derivado da classe B, então os métodos virtuais chamados serão aqueles de B, em vez de A, desde que você passa pela tabela de funções B aponta para.


Métodos virtuais são usados ​​no design da interface. Por exemplo, no Windows há uma interface chamada IUnknown como abaixo:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

Esses métodos são deixados para o usuário da interface implementar. Eles são essenciais para a criação e destruição de certos objetos que devem herdar IUnknown. Nesse caso, o tempo de execução está ciente dos três métodos e espera que eles sejam implementados quando os chama. Então, em certo sentido, eles agem como um contrato entre o objeto em si e o que quer que use esse objeto.


Meus 2 centavos

Em C ++, funções virtuais são necessárias para realizar o polimorfismo de subtipo .

fundo

Exemplo de código

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for interface classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

Técnica atrás

vtables

Veja a contribuição de rvkreddy.

A segunda melhor resposta

Veja a contribuição de MJ.


A palavra-chave virtual força o compilador a escolher a implementação do método definida na classe do objeto, e não na classe do ponteiro .

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

No exemplo acima, Shape :: getName será chamado por padrão, a menos que o getName () seja definido como virtual na Shape da classe Base. Isso força o compilador a procurar a implementação getName () na classe Triangle e não na classe Shape.

A tabela virtual é o mecanismo no qual o compilador controla as várias implementações de método virtual das subclasses. Isso também é chamado de despacho dinâmico e há alguma sobrecarga associada a ele.

Por fim, por que o virtual é mesmo necessário em C ++, por que não torná-lo o comportamento padrão como em Java?

  1. C ++ é baseado nos princípios de "Zero Overhead" e "Pague pelo que você usa". Portanto, ele não tenta executar o despacho dinâmico para você, a menos que você precise.
  2. Para fornecer mais controle para a interface. Ao tornar uma função não virtual, a classe interface / abstract pode controlar o comportamento em todas as suas implementações.

Eu gostaria de adicionar outro uso da função Virtual, embora use o mesmo conceito que as respostas acima, mas acho que vale a pena mencionar.

DESTRUTOR VIRTUAL

Considere este programa abaixo, sem declarar o destruidor da classe Base como virtual; a memória do Cat pode não ser limpa.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Saída:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Saída:

Deleting an Animal name Cat
Deleting an Animal

Necessidade de função virtual explicada [Fácil de entender]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

Saída será:

Hello from Class A.

Mas com função virtual:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

Saída será:

Hello from Class B.

Portanto, com a função virtual, você pode obter o polimorfismo de tempo de execução.





virtual-functions