Virtual / virtual puro explicado


346

O que exatamente significa se uma função é definida como virtual e é a mesma coisa que virtual puro?

Respostas:


339

Da função virtual da Wikipedia ...

Na programação orientada a objetos, em linguagens como C ++ e Object Pascal, uma função virtual ou método virtual é uma função ou método herdável e substituível para o qual é facilitado o envio dinâmico. Esse conceito é uma parte importante da parte do polimorfismo (tempo de execução) da programação orientada a objetos (OOP). Em resumo, uma função virtual define uma função de destino a ser executada, mas o destino pode não ser conhecido no momento da compilação.

Ao contrário de uma função não virtual, quando uma função virtual é substituída, a versão mais derivada é usada em todos os níveis da hierarquia de classes, em vez de apenas no nível em que foi criada. Portanto, se um método da classe base chamar um método virtual, a versão definida na classe derivada será usada em vez da versão definida na classe base.

Isso contrasta com as funções não virtuais, que ainda podem ser substituídas em uma classe derivada, mas a versão "nova" será usada apenas pela classe derivada e abaixo, mas não alterará a funcionalidade da classe base.

enquanto que..

Uma função virtual pura ou método virtual puro é uma função virtual que deve ser implementada por uma classe derivada se a classe derivada não for abstrata.

Quando existe um método virtual puro, a classe é "abstrata" e não pode ser instanciada sozinha. Em vez disso, uma classe derivada que implementa o (s) método (s) virtual puro (s) deve ser usada. Um virtual puro não é definido na classe base, portanto, uma classe derivada deve defini-la ou essa classe derivada também é abstrata e não pode ser instanciada. Somente uma classe que não possui métodos abstratos pode ser instanciada.

Um virtual fornece uma maneira de substituir a funcionalidade da classe base, e um virtual puro exige isso.


10
Então ... virtual puro é uma palavra-chave ou apenas um termo usado?
23410 Justin

197
Função void virtual () = 0; é um virtual puro. O "= 0" indica é pureza.
Goz

8
Justin, 'virtual puro' é apenas um termo (não uma palavra-chave, veja minha resposta abaixo) usado para significar "essa função não pode ser implementada pela classe base. Como Goz disse, adicionando" = 0 "ao final de um virtual função torna "puro"
Nick Haddad

14
Acredito que Stroustrup disse que queria adicionar uma purepalavra - chave, mas que o Bell Labs estava prestes a lançar um grande lançamento de C ++, e seu gerente não o permitiria nessa fase tardia. Adicionar palavras-chave é importante.
quark

14
Esta não é uma boa resposta. Qualquer método pode ser substituído, não apenas os virtuais. Veja minha resposta para mais detalhes.
Asik

212

Eu gostaria de comentar sobre a definição de virtual da Wikipedia, conforme repetido por vários aqui. [No momento em que essa resposta foi escrita], a Wikipedia definia um método virtual como aquele que pode ser substituído em subclasses. [Felizmente, a Wikipedia foi editada desde então e agora explica isso corretamente.] Isso está incorreto: qualquer método, não apenas o virtual, pode ser substituído em subclasses. O que o virtual faz é fornecer polimorfismo, ou seja, a capacidade de selecionar em tempo de execução a substituição mais derivada de um método .

Considere o seguinte código:

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

Qual é o resultado deste programa?

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

Derivado substitui todos os métodos do Base: não apenas o virtual, mas também o não virtual.

Vemos que, quando você tem um ponteiro de base para derivado (bDerived), chamar NonVirtual chama a implementação da classe Base. Isso é resolvido no momento da compilação: o compilador vê que bDerived é uma Base *, que NonVirtual não é virtual e, portanto, executa a resolução na classe Base.

No entanto, chamar Virtual chama a implementação da classe Derived. Por causa da palavra-chave virtual, a seleção do método acontece no tempo de execução , não no tempo de compilação. O que acontece aqui no momento da compilação é que o compilador vê que isso é uma Base * e que está chamando um método virtual; portanto, insere uma chamada na vtable em vez da classe Base. Essa tabela é instanciada no tempo de execução, portanto, a resolução do tempo de execução para a substituição mais derivada.

Espero que não tenha sido muito confuso. Em resumo, qualquer método pode ser substituído, mas apenas os métodos virtuais fornecem polimorfismo, ou seja, seleção em tempo de execução da substituição mais derivada. Na prática, no entanto, substituir um método não virtual é considerado uma prática ruim e raramente usada; muitas pessoas (incluindo quem escreveu esse artigo da Wikipedia) acham que apenas os métodos virtuais podem ser substituídos.


6
Só porque o artigo da Wikipedia (que não sou de maneira alguma defender) define um método virtual "como aquele que pode ser substituído em subclasses" não exclui a possibilidade de que outros métodos não virtuais com o mesmo nome possam ser declarados. Isso é conhecido como sobrecarga.

26
No entanto, a definição está incorreta. Um método que pode ser substituído em uma classe derivada não é virtual por definição; se o método pode ser substituído é irrelevante para a definição de "virtual". Além disso, "sobrecarga" geralmente se refere a ter vários métodos com o mesmo nome e tipo de retorno, mas com argumentos diferentes, na mesma classe; é muito diferente de "substituir", que implica exatamente a mesma assinatura, mas em uma classe derivada. Quando é feito de forma não polimórfica (base não virtual), costuma ser chamado de "ocultação".
Asik

5
Essa deve ser a resposta aceita. Esse artigo específico da Wikipedia, que levarei algum tempo para vincular aqui, já que ninguém mais fez isso , é um lixo completo. +1, bom senhor.
Josaphatv # 9/14

2
Agora faz sentido. Obrigado, bom senhor, por explicar adequadamente que qualquer método pode ser substituído por classes derivadas e a mudança está na maneira como o compilador se comportará para escolher qual função é chamada em diferentes situações.
Doodad

3
Pode ser útil adicionar uma Derived*com as mesmas chamadas de função para levar o ponto para casa. Caso contrário, grande resposta
Jeff Jones

114

A palavra-chave virtual fornece ao C ++ sua capacidade de suportar polimorfismo. Quando você tem um ponteiro para um objeto de alguma classe, como:

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();
}

Neste exemplo (bobo), a função GetNumberOfLegs () retorna o número apropriado com base na classe do objeto para o qual é chamado.

Agora, considere a função 'SomeFunction'. Ele não se importa com o tipo de objeto animal que é passado a ele, desde que seja derivado de Animal. O compilador converterá automaticamente qualquer classe derivada de Animal para um Animal, pois é uma classe base.

Se fizermos isso:

Duck d;
SomeFunction(&d);

seria '2'. Se fizermos isso:

Horse h;
SomeFunction(&h);

seria '4'. Não podemos fazer isso:

Animal a;
SomeFunction(&a);

porque não será compilado devido à função virtual GetNumberOfLegs () ser pura, o que significa que deve ser implementada derivando classes (subclasses).

As funções virtuais puras são usadas principalmente para definir:

a) classes abstratas

Essas são classes base nas quais você deve derivar delas e implementar as funções virtuais puras.

b) interfaces

Essas são classes 'vazias', onde todas as funções são puramente virtuais e, portanto, você deve derivar e implementar todas as funções.


No seu exemplo, você não pode fazer o nº 4 porque não forneceu uma implementação do método virtual puro. Não é estritamente porque o método é totalmente virtual.
Iheanyi

@iheanyi Você não pode fornecer implementação para o método virtual puro na classe base. Portanto, o caso 4 ainda é um erro.
prasad

32

Em uma classe C ++, virtual é a palavra-chave que designa isso, um método pode ser substituído (isto é, implementado por) uma subclasse. Por exemplo:

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

Nesse caso, uma subclasse pode substituir a função initShape para realizar algum trabalho especializado:

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

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

O termo virtual puro refere-se a funções virtuais que precisam ser implementadas por uma subclasse e não foram implementadas pela classe base. Você designa um método como virtual puro usando a palavra-chave virtual e adicionando a = 0 no final da declaração do método.

Portanto, se você deseja tornar o Shape :: initShape puro virtual, faça o seguinte:

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

Ao adicionar um método virtual puro à sua classe, você torna a classe uma classe base abstrata que é muito útil para separar interfaces da implementação.


11
Em relação às "funções virtuais que devem ser implementadas por uma subclasse" - isso não é rigorosamente verdade, mas a subclasse também é abstrata se não for. E classes abstratas não podem ser instanciadas. Além disso, "não pode ser implementado pela classe base" parece enganador; Eu sugeriria que "não foi" seria melhor, pois não há restrição às modificações do código para adicionar uma implementação na classe base.
NVRAM

2
E "a função getName não pode ser implementada por uma subclasse" não está certa. As subclasses podem implementar o método (com a mesma assinatura ou com outra assinatura), mas essa implementação não substituirá o método. Você poderia implementar Circle como uma subclasse e implementar "std :: string Circle :: getName ()" - então você poderia chamar qualquer método para uma instância Circle. Mas se usado por meio de um ponteiro ou referência do Shape, o compilador chamaria Shape :: getName ().
NVRAM

11
Bons pontos em ambas as frentes. Eu estava tentando evitar discutir casos especiais para este exemplo. Modificarei a resposta para ser mais tolerante. Obrigado!
21411 Nick Haddad

@NickHaddad Tópico antigo, mas querendo saber por que você chamou sua variável m_name. O que m_significa isso?
Tqn

11
@Tqn assumindo que NickHaddad seguiu convenções, m_name é uma convenção de nomenclatura comumente chamada de notação húngara. O m indica membro de uma estrutura / classe, número inteiro.
Ketcomp 17/07/2015

16

"Virtual" significa que o método pode ser substituído em subclasses, mas possui uma implementação diretamente chamada na classe base. "Virtual puro" significa que é um método virtual sem implementação de chamada direta. Esse método deve ser substituído pelo menos uma vez na hierarquia de herança - se uma classe tiver métodos virtuais não implementados, os objetos dessa classe não poderão ser construídos e a compilação falhará.

O @quark indica que os métodos puramente virtuais podem ter uma implementação, mas como os métodos puramente virtuais devem ser substituídos, a implementação padrão não pode ser chamada diretamente. Aqui está um exemplo de um método virtual puro com um padrão:

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

Segundo os comentários, se a compilação falhará ou não é específico do compilador. No GCC 4.3.3, pelo menos, ele não compila:

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

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

Resultado:

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

deve ser substituído se você deseja instanciar uma instância da classe. Se você não criar nenhuma instância, o código será compilado corretamente.
Glen

11
compilação não falhará. Se não houver implementação de um método virtual (puro), essa classe / objeto não poderá ser instanciado. Pode não ser LINK, mas será compilado.
Tim

@Glen, @tim: em qual compilador? Quando tento compilar um programa que cria uma classe abstrata, ele não é compilado.
John Millikin

@ John Compilation falhará apenas se você tentar instanciar uma instância de uma classe que contenha um PVF. É claro que você pode instanciar valores de ponteiro ou referência para essas classes.

5
Além disso, John, o seguinte não está certo: "'Virtual puro' significa que é um método virtual sem implementação". Métodos virtuais puros podem ter implementações. Mas você não pode chamá-los diretamente: você deve substituir e usar a implementação da classe base de dentro da subclasse. Isso permite que você forneça uma parte padrão da implementação. Porém, não é uma técnica comum.
quark

9

Como a palavra-chave virtual funciona?

Suponha que o homem seja uma classe base, o indiano é derivado do homem.

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

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

Declarar do_work () como virtual significa simplesmente: qual do_work () chamar será determinado SOMENTE no tempo de execução.

Suponha que sim,

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

Se virtual não for usado, o mesmo será determinado estaticamente ou vinculado estaticamente pelo compilador, dependendo de qual objeto está chamando. Portanto, se um objeto do homem chama do_work (), o do_work () do homem é chamado MESMO, embora aponte para um objeto indiano

Acredito que a resposta mais votada é enganosa - Qualquer método, virtual ou não, pode ter uma implementação substituída na classe derivada. Com referência específica ao C ++, a diferença correta é a ligação em tempo de execução (quando o virtual é usado) e o tempo de compilação (quando o virtual não é usado, mas um método é substituído e um ponteiro base é apontado para um objeto derivado) das funções associadas.

Parece haver outro comentário enganoso que diz:

"Justin, 'virtual puro' é apenas um termo (não uma palavra-chave, veja minha resposta abaixo) usado para significar" esta função não pode ser implementada pela classe base. "

ISTO ESTÁ ERRADO! Funções puramente virtuais também podem ter um corpo E PODEM SER IMPLEMENTADAS! A verdade é que a função virtual pura de uma classe abstrata pode ser chamada estaticamente! Dois autores muito bons são Bjarne Stroustrup e Stan Lippman .... porque eles escreveram a linguagem.


2
Infelizmente, quando uma resposta começar a ser votada, todas as outras serão ignoradas. Mesmo que eles pudessem ser melhores.
LtWorf

3

Uma função virtual é uma função de membro declarada em uma classe base e redefinida pela classe derivada. A função virtual é hierárquica em ordem de herança. Quando uma classe derivada não substitui uma função virtual, a função definida em sua classe base é usada.

Uma função virtual pura é aquela que não contém definição em relação à classe base. Não possui implementação na classe base. Qualquer classe derivada deve substituir essa função.


2

Simula, C ++ e C #, que usam a ligação estática de métodos por padrão, o programador pode especificar que métodos específicos usem a ligação dinâmica, rotulando-os como virtuais. A ligação de método dinâmico é fundamental para a programação orientada a objetos.

A programação orientada a objetos requer três conceitos fundamentais: encapsulamento, herança e ligação dinâmica de métodos.

O encapsulamento permite ocultar os detalhes de implementação de uma abstração atrás de uma interface simples.

A herança permite que uma nova abstração seja definida como uma extensão ou refinamento de alguma abstração existente, obtendo algumas ou todas as suas características automaticamente.

A ligação de método dinâmico permite que a nova abstração exiba seu novo comportamento mesmo quando usado em um contexto que espera a abstração antiga.


1

Os métodos virtuais PODEM ser substituídos pelas classes derivadas, mas precisam de uma implementação na classe base (aquela que será substituída)

Métodos virtuais puros não têm implementação na classe base. Eles precisam ser definidos por classes derivadas. (Portanto, tecnicamente substituído não é o termo certo, porque não há nada a substituir).

Virtual corresponde ao comportamento java padrão, quando a classe derivada substitui um método da classe base.

Os métodos puros virtuais correspondem ao comportamento dos métodos abstratos nas classes abstratas. E uma classe que contém apenas métodos e constantes virtuais puros seria o cpp-pendant para uma Interface.


0

Função Virtual Pura

tente este código

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

Na classe anotherClass, remova a função sayHellow e execute o código. você receberá um erro! Como quando uma classe contém uma função virtual pura, nenhum objeto pode ser criado a partir dessa classe e ele é herdado, então sua classe derivada deve implementar essa função.

Função virtual

tente outro código

#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!

}

Aqui, a função sayHellow é marcada como virtual na classe base. Diz o compilador que tenta pesquisar a função na classe derivada e implementa a função.Se não encontrado, execute a base one.Obrigado


Haha, ele me levou um longo 30 segundos para entender o que está errado aqui ... hellow :)
hans

0

"Uma função virtual ou método virtual é uma função ou método cujo comportamento pode ser substituído em uma classe herdada por uma função com a mesma assinatura" - wikipedia

Esta não é uma boa explicação para funções virtuais. Porque, mesmo que um membro não seja virtual, a herança de classes pode substituí-lo. Você pode tentar e ver você mesmo.

A diferença aparece quando uma função recebe uma classe base como parâmetro. Quando você fornece uma classe herdada como entrada, essa função usa a implementação da classe base da função substituída. No entanto, se essa função é virtual, ela usa a que é implementada na classe derivada.


0
  • As funções virtuais devem ter uma definição na classe base e também na classe derivada, mas não necessárias, por exemplo, a função ToString () ou toString () é uma Virtual, para que você possa fornecer sua própria implementação, substituindo-a nas classes definidas pelo usuário.

  • Funções virtuais são declaradas e definidas na classe normal.

  • A função virtual pura deve ser declarada terminando com "= 0" e só pode ser declarada na classe abstrata.

  • Uma classe abstrata que possui uma (s) função (s) virtual (ais) pura (s) não pode ter uma (s) definição (ões) dessa (s) função (s) virtual (is) pura (s), portanto, implica que a implementação deve ser fornecida nas classes que derivaram dessa classe abstrata.


Mesma nota que a @rashedcs: Na verdade uma função virtual pura pode ter sua definição ...
Jarek C
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.