Método virtual privado em C ++

125

Qual é a vantagem de tornar virtual um método privado em C ++?

Eu notei isso em um projeto C ++ de código aberto:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
Silverburgh
fonte
9
Eu acho que a pergunta está ao contrário. O motivo de tornar algo virtual é sempre o mesmo: permitir que classes derivadas o substituam. Portanto, a pergunta deve ser: qual é a vantagem de tornar um método virtual privado? Para qual a resposta é: torne tudo privado por padrão. :-)
ShreevatsaR
1
@ShreevatsaR Mas você nem sequer responder a sua própria pergunta ......
Spencer
@ShreevatsaR Pensei que você quisesse retroceder de uma maneira diferente: qual é a vantagem de tornar um método virtual não privado?
Peter - Restabelece Monica

Respostas:

115

Herb Sutter explicou muito bem aqui .

Diretriz 2: Prefira tornar as funções virtuais privadas.

Isso permite que as classes derivadas substituam a função para personalizar o comportamento, conforme necessário, sem expor ainda mais as funções virtuais, tornando-as invocáveis ​​por classes derivadas (como seria possível se as funções fossem apenas protegidas). O ponto é que as funções virtuais existem para permitir a personalização; a menos que eles também precisem ser invocados diretamente do código das classes derivadas, não há necessidade de torná-las nada além de privadas

Prasoon Saurav
fonte
Como você pode adivinhar pela minha resposta, acho que a diretriz nº 3 de Sutter empurra a diretriz nº 2 pela janela.
Spencer #
66

Se o método for virtual, ele poderá ser substituído por classes derivadas, mesmo que seja privado. Quando o método virtual é chamado, a versão substituída será chamada.

(Ao contrário de Herb Sutter citado por Prasoon Saurav em sua resposta, o C ++ FAQ Lite recomenda contra virtuais virtuais , principalmente porque muitas vezes confunde as pessoas.)

sth
fonte
41
Parece que o C ++ FAQ Lite mudou sua recomendação desde então: " o C ++ FAQ anteriormente recomendava o uso de virtuais protegidos em vez de virtuais privados. No entanto, a abordagem virtual privada agora é comum o suficiente para que a confusão de iniciantes seja menos preocupante. "
Zack The humano
19
Confusão de especialistas, no entanto, continua sendo uma preocupação. Nenhum dos quatro profissionais de C ++ sentados ao meu lado tinham conhecimento de virtuais virtuais.
Newtonx 20/04
12

Apesar de todas as chamadas para declarar um membro virtual privado, o argumento simplesmente não é válido. Freqüentemente, a substituição de uma função virtual por uma classe derivada precisará chamar a versão da classe base. Não pode se for declarado private:

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Você precisa declarar o método da classe base protected.

Então, você deve tomar o feio expediente de indicar, por meio de um comentário, que o método deve ser substituído, mas não chamado.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Assim, a diretriz 3 de Herb Sutter ... Mas o cavalo está fora do estábulo de qualquer maneira.

Quando você declara algo protectedque está implicitamente confiando no escritor de qualquer classe derivada para entender e usar adequadamente os internos protegidos, da mesma maneira que uma frienddeclaração implica em uma confiança mais profunda para os privatemembros.

Os usuários que têm mau comportamento por violar essa confiança (por exemplo, rotulados como 'sem noção' por não se preocuparem em ler sua documentação) são os únicos culpados.

Atualizar : recebi alguns comentários que afirmam que você pode "encadear" implementações de funções virtuais dessa maneira usando funções virtuais privadas. Se sim, com certeza gostaria de vê-lo.

Os compiladores C ++ que eu uso definitivamente não permitem que uma implementação de classe derivada chame uma implementação de classe base privada.

Se o comitê C ++ relaxasse "private" para permitir esse acesso específico, eu seria totalmente a favor de funções virtuais privadas. Como está, ainda estamos sendo aconselhados a trancar a porta do celeiro depois que o cavalo for roubado.

Spencer
fonte
3
Acho seu argumento inválido. Você, como desenvolvedor de uma API, deve procurar uma interface que seja difícil de usar incorretamente e não configurar outro desenvolvedor para seus próprios erros ao fazê-lo. O que você deseja fazer no seu exemplo pode ser implementado apenas com métodos virtuais privados.
Sigy
1
Não era isso que eu estava dizendo. Mas você pode reestruturar seu código para conseguir o mesmo efeito sem a necessidade de chamar uma função de classe base privada
sigy
3
No seu exemplo, você deseja estender o comportamento de set_data. Instruções m_data = ndata;e, cleanup();portanto, pode ser considerado invariável, que deve ser válido para todas as implementações. Torne, portanto, cleanup()não virtual e privado. Adicione uma chamada a outro método privado virtual e o ponto de extensão da sua classe. Agora não há mais necessidade de suas classes derivadas chamarem a base cleanup(), seu código permanece limpo e sua interface é difícil de usar incorretamente.
Sigy
2
@ sigy Isso apenas move os postes. Você precisa olhar além do exemplo inicial. Quando há mais descendentes que precisam chamar todos os cleanup()s na cadeia, o argumento se desfaz. Ou você está recomendando uma função virtual extra para cada descendente da cadeia? Ick. Até Herb Sutter permitiu funções virtuais protegidas como uma brecha em sua diretriz nº 3. De qualquer forma, sem um código real, você nunca vai me convencer.
Spencer
2
Então vamos concordar em discordar;) #
sigy
9

Encontrei esse conceito pela primeira vez ao ler o 'Effective C ++' de Scott Meyers, item 35: considere alternativas às funções virtuais. Eu queria fazer referência a Scott Mayers para outros que possam estar interessados.

Faz parte do padrão do método de modelo através do idioma da interface não virtual : os métodos voltados para o público não são virtuais; em vez disso, eles agrupam as chamadas de método virtual que são privadas. A classe base pode executar a lógica antes e depois da chamada da função virtual privada:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

Acho que esse é um padrão de design muito interessante e tenho certeza de que você pode ver como o controle adicionado é útil.

  • Por que fazer a função virtual private? A melhor razão é que já fornecemos um publicmétodo de enfrentamento.
  • Por que não fazê- protectedlo simplesmente para que eu possa usar o método para outras coisas interessantes? Suponho que sempre dependerá do seu design e de como você acredita que a classe base se encaixa. Eu diria que o criador da classe derivada deve se concentrar na implementação da lógica necessária; tudo o resto já está resolvido. Além disso, há a questão do encapsulamento.

Da perspectiva do C ++, é completamente legítimo substituir um método virtual privado, mesmo que você não possa chamá-lo da sua classe. Isso suporta o design descrito acima.

Pooven
fonte
3

Eu os uso para permitir que as classes derivadas "preencham os espaços em branco" para uma classe base sem expor esse buraco aos usuários finais. Por exemplo, tenho objetos altamente stateful derivados de uma base comum, que só pode implementar 2/3 da máquina de estado geral (as classes derivadas fornecem o 1/3 restante, dependendo do argumento do modelo, e a base não pode ser um modelo para outras razões).

EU PRECISO ter a classe base comum para fazer com que muitas APIs públicas funcionem corretamente (estou usando modelos variados), mas não posso deixar esse objeto sair para a natureza. Pior, se eu deixar as crateras na máquina de estado - na forma de funções virtuais puras - em qualquer lugar, exceto em "Privado", permito que um usuário inteligente ou sem noção derivado de uma de suas classes filho substitua os métodos que os usuários nunca devem tocar. Então, eu coloquei 'cérebros' da máquina de estado em funções virtuais PRIVADAS. Em seguida, os filhos imediatos da classe base preenchem os espaços em branco em suas substituições NÃO virtuais, e os usuários podem usar com segurança os objetos resultantes ou criar suas próprias classes derivadas adicionais sem se preocupar em estragar a máquina de estado.

Quanto ao argumento de que você não deve ter métodos virtuais públicos, digo BS. Os usuários podem substituir indevidamente os virtuais privados tão facilmente quanto os públicos - eles estão definindo novas classes, afinal. Se o público não deve modificar uma determinada API, não a torne virtual em objetos acessíveis ao público.

Zack Yezek
fonte