Qual é o objetivo de uma função virtual pura privada?

139

Me deparei com o seguinte código em um arquivo de cabeçalho:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

Para mim, isso implica que a Engineclasse ou uma classe derivada dela precisa fornecer a implementação para essas funções virtuais puras. Mas não achei que as classes derivadas pudessem ter acesso a essas funções privadas para reimplementá-las - então, por que torná-las virtuais?

BeeBand
fonte

Respostas:

209

A pergunta no tópico sugere uma confusão bastante comum. A confusão é bastante comum, que o C ++ FAQ defendeu contra o uso de virtuais privados, por um longo tempo, porque a confusão parecia ser uma coisa ruim.

Portanto, para se livrar da confusão primeiro: Sim, funções virtuais privadas podem ser substituídas nas classes derivadas. Os métodos de classes derivadas não podem chamar funções virtuais da classe base, mas podem fornecer sua própria implementação para eles. De acordo com Herb Sutter, ter uma interface pública não virtual na classe base e uma implementação privada que pode ser personalizada nas classes derivadas, permite uma melhor "separação da especificação da interface da especificação do comportamento personalizável da implementação". Você pode ler mais sobre isso em seu artigo "Virtualidade" .

Há, no entanto, mais uma coisa interessante no código que você apresentou, que merece mais atenção, na minha opinião. A interface pública consiste em um conjunto de funções não virtuais sobrecarregadas e essas funções chamam funções virtuais não públicas e não sobrecarregadas. Como de costume no mundo C ++, é um idioma, tem um nome e, claro, é útil. O nome é (surpresa, surpresa!)

"Públicos não virtuais sobrecarregados chamam virtuais não sobrecarregados protegidos"

Ajuda a gerenciar adequadamente a regra de ocultação . Você pode ler mais sobre isso aqui , mas tentarei explicar em breve.

Imagine que as funções virtuais da Engineclasse também são sua interface e é um conjunto de funções sobrecarregadas que não é virtual puro. Se fossem virtuais, ainda era possível encontrar o mesmo problema, conforme descrito abaixo, mas menor na hierarquia de classes.

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

Agora vamos assumir que você deseja criar uma classe derivada e precisa fornecer uma nova implementação apenas para o método, que usa duas entradas como argumentos.

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Se você esqueceu de colocar a declaração de uso na classe derivada (ou redefinir a segunda sobrecarga), poderá ter problemas no cenário abaixo.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Se você não impediu a ocultação dos Enginemembros, a declaração:

myV8->SetState(5, true);

chamaria void SetState( int var, int val )da classe derivada, convertendo truepara int.

Se a interface não for virtual e a implementação virtual não for pública, como no seu exemplo, o autor da classe derivada terá menos um problema para pensar e pode simplesmente escrever

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
Maciej Hehl
fonte
por que a função virtual precisa ser privada? Pode ser público?
Rich
Gostaria de saber se as diretrizes dadas por Herb Sutter em seu artigo "Virtualidade" ainda são válidas hoje?
N
@ Rich Você poderia, mas ao torná-los não públicos, você pode transmitir sua intenção com mais clareza. Primeiro, ele mostra uma separação de preocupações se você continuar a tornar a interface pública e a implementação não pública. Segundo, se você deseja que as classes herdadas possam chamar as implementações de base, pode declara-las protegidas; se você deseja que eles forneçam suas próprias implementações sem chamar as de base, você as torna privadas.
Dan
43

A função virtual pura privada é a base do idioma da interface não virtual (OK, nem sempre é virtual pura , mas ainda é virtual). Obviamente, isso também é usado para outras coisas, mas acho isso mais útil (: em duas palavras: em uma função pública, você pode colocar algumas coisas comuns (como registro, estatísticas etc.) no início e no final da função e, em seguida, "no meio" para chamar essa função virtual privada, que será diferente para a classe derivada específica.

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Virtual puro - apenas obriga as classes derivadas a implementá-lo.

EDIT : Mais sobre isso: Wikipedia :: NVI-idiom

Kiril Kirov
fonte
17

Bem, por um lado, isso permitiria que uma classe derivada implementasse uma função que a classe base (contendo a declaração pura da função virtual) possa chamar.

Michael Goldshteyn
fonte
5
que somente a classe base pode chamar!
underscore_d
4

EDIT: Declarações esclarecidas sobre a capacidade de substituir e a capacidade de acessar / chamar.

Ele poderá substituir essas funções privadas. Por exemplo, o exemplo inventado a seguir funciona ( EDIT: tornou privado o método da classe derivada e solte a invocação do método da classe derivada main()para demonstrar melhor a intenção do padrão de design em uso. ):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualOs métodos em uma classe base como os do seu código geralmente são usados ​​para implementar o padrão de design do Método de Modelo . Esse padrão de design permite alterar o comportamento de um algoritmo na classe base sem alterar o código na classe base. O código acima, onde os métodos da classe base são chamados por meio de um ponteiro da classe base, é um exemplo simples do padrão Método do Modelo.

Vazio
fonte
Entendo, mas se as classes derivadas têm algum tipo de acesso, por que se preocupar em torná-las privadas?
BeeBand 19/10/10
@BeeBand: o usuário teria acesso às substituições do método virtual da classe derivada pública, mas não teria acesso às da classe base. O autor da classe derivada, nesse caso, também pode manter as substituições do método virtual. Na verdade, farei a alteração no código de exemplo acima para enfatizar isso. De qualquer maneira, eles sempre poderiam herdar publicamente e substituir os métodos virtuais da classe base privada, mas ainda assim teriam acesso apenas aos seus próprios métodos virtuais da classe derivada. Observe que estou fazendo uma distinção entre substituição e acesso / invocação.
Vazio
porque você está errado. a visibilidade da herança entre classes Enginee DerivedEnginenão tem nada a ver com o que DerivedEnginepode ou não pode substituir (ou acessar, nesse caso).
wilhelmtell
@wilhelmtell: suspiro Claro que você está correto. Vou atualizar minha resposta de acordo.
Anula
3

O método virtual privado é usado para limitar o número de classes derivadas que podem substituir a função especificada. As classes derivadas que precisam substituir o método virtual privado precisarão ser amigas da classe base.

Uma breve explicação pode ser encontrada no DevX.com .


EDITAR Um método virtual privado é efetivamente usado no Template Method Pattern . As classes derivadas podem substituir o método virtual privado, mas as classes derivadas não podem chamá-lo de método virtual privado de classe base (no seu exemplo SetStateBoole SetStateInt). Somente a classe base pode efetivamente chamar seu método virtual privado ( somente se as classes derivadas precisarem invocar a implementação básica de uma função virtual, torne a função virtual protegida ).

Um artigo interessante pode ser encontrado sobre a virtualidade .

Buhake Sindi
fonte
2
@ Gentleman ... hmmm role para baixo até o comentário de Colin D Bennett. Ele parece pensar que "uma função virtual privada pode ser substituída por classes derivadas, mas só pode ser chamada de dentro da classe base". @ Michael Goldshteyn também pensa assim também.
BeeBand 19/10/10
Eu acho que você esqueceu o princípio de que uma classe privada não pode ser vista por sua classe derivada. Essas são as regras OOP e se aplicam a todos os idiomas que são OOP. Para que uma classe derivada implemente seu método virtual privado de classe base, ela deve friendpertencer à classe base. O Qt adotou a mesma abordagem ao implementar seu modelo de documento XML DOM.
Buhake Sindi 19/10/10
@ Gentleman: Não, eu não esqueci. Eu cometi um erro de digitação no meu comentário. Em vez de "acesso aos métodos da classe base", eu deveria ter escrito "pode ​​substituir os métodos da classe base". A classe derivada certamente pode substituir o método da classe base virtual privada, mesmo que não possa acessar esse método da classe base. O artigo do DevX.com que você apontou estava incorreto (herança pública). Experimente o código na minha resposta. Apesar do método da classe base virtual privada, a classe derivada é capaz de substituí-lo. Não vamos confundir a capacidade de substituir um método de classe base virtual privada com a capacidade de invocá-lo.
Vazio
@ Gentleman: @ wilhelmtell apontou o erro na minha resposta / comentário. Minha afirmação sobre herança que afeta a acessibilidade de classe derivada do método de classe base estava desativada. Removemos o comentário ofensivo à sua resposta.
Anula
@ Void, vejo que uma classe derivada pode substituir seu método virtual privado de classe base, mas não pode usá-lo. Portanto, este é essencialmente um padrão de método de modelo.
Buhake Sindi
0

Resposta TL; DR:

Você pode tratá-lo como outro nível de encapsulamento - em algum lugar entre protegido e privado : você não pode chamá-lo da classe filho, mas pode substituí-lo.

É útil ao implementar o padrão de design do Método de Modelo . Você pode usar protegido , mas o privado, juntamente com o virtual, pode ser considerado a melhor opção, devido ao melhor encapsulamento.

jaskmar
fonte