Ao contrário da herança protegida, a herança privada C ++ encontrou seu caminho para o desenvolvimento C ++ convencional. No entanto, ainda não encontrei um bom uso para isso.
Quando vocês usam?
Nota após a aceitação da resposta: esta NÃO é uma resposta completa. Leia outras respostas como aqui (conceitualmente) e aqui (teórica e prática) se você estiver interessado na pergunta. Este é apenas um truque sofisticado que pode ser alcançado com a herança privada. Embora seja extravagante , não é a resposta à pergunta.
Além do uso básico de apenas herança privada mostrado no C ++ FAQ (vinculado em comentários de outros), você pode usar uma combinação de herança privada e virtual para selar uma classe (na terminologia .NET) ou para tornar uma classe final (na terminologia Java) . Este não é um uso comum, mas de qualquer maneira eu achei interessante:
class ClassSealer {
private:
friend class Sealed;
ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{
// ...
};
class FailsToDerive : public Sealed
{
// Cannot be instantiated
};
Selado pode ser instanciado. Ele deriva de ClassSealer e pode chamar o construtor privado diretamente, pois é um amigo.
FailsToDerive não compilará, pois deve chamar o construtor ClassSealer diretamente (requisito de herança virtual), mas não pode, pois é privado na classe Sealed e, neste caso, FailsToDerive não é amigo de ClassSealer .
EDITAR
Foi mencionado nos comentários que isso não poderia ser tornado genérico na época usando o CRTP. O padrão C ++ 11 remove essa limitação, fornecendo uma sintaxe diferente para se tornar amigo dos argumentos do modelo:
template <typename T>
class Seal {
friend T; // not: friend class T!!!
Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...
Claro que tudo isso é discutível, já que o C ++ 11 fornece uma final
palavra-chave contextual exatamente para este propósito:
class Sealed final // ...
Eu uso isso o tempo todo. Alguns exemplos em cima da minha cabeça:
Um exemplo típico é derivar de forma privada de um contêiner STL:
fonte
push_back
,MyVector
obtenha-as gratuitamente.template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }
ou pode escrever usandoBase::f;
. Se você quer a maioria da funcionalidade e flexibilidade que a herança privada e umausing
declaração dá-lhe, você tem esse monstro para cada função (e não se esqueçaconst
evolatile
sobrecargas!).O uso canônico de herança privada é o relacionamento "implementado em termos de" (graças a Scott Meyers em '' C ++ Efetivo 'por este texto). Em outras palavras, a interface externa da classe herdada não tem nenhum relacionamento (visível) com a classe herdada, mas a usa internamente para implementar sua funcionalidade.
fonte
Um uso útil da herança privada é quando você tem uma classe que implementa uma interface, que é então registrada com algum outro objeto. Você torna essa interface privada para que a própria classe tenha que se registrar e apenas o objeto específico com o qual ela está registrada possa usar essas funções.
Por exemplo:
Portanto, a classe FooUser pode chamar os métodos privados de FooImplementer por meio da interface FooInterface, enquanto outras classes externas não podem. Este é um ótimo padrão para lidar com retornos de chamada específicos que são definidos como interfaces.
fonte
Acho que a seção crítica do C ++ FAQ Lite é:
Em caso de dúvida, você deve preferir a composição à herança privada.
fonte
Acho útil para interfaces (viz. Classes abstratas) que estou herdando onde não quero que outro código toque na interface (apenas a classe herdada).
[editado em um exemplo]
Veja o exemplo relacionado acima. Dizendo isso
é para dizer que Wilma está exigindo que Fred seja capaz de invocar certas funções-membro, ou melhor, está dizendo que Wilma é uma interface . Portanto, conforme mencionado no exemplo
comentários sobre o efeito desejado dos programadores que precisam atender aos nossos requisitos de interface ou quebrar o código. E, uma vez que fredCallsWilma () é protegido, apenas amigos e classes derivadas podem tocá-lo, ou seja, uma interface herdada (classe abstrata) que apenas a classe herdeira pode tocar (e amigos).
[editado em outro exemplo]
Esta página discute brevemente as interfaces privadas (ainda de outro ângulo).
fonte
Às vezes, acho útil usar herança privada quando desejo expor uma interface menor (por exemplo, uma coleção) na interface de outra, onde a implementação da coleção requer acesso ao estado da classe exposta, de maneira semelhante às classes internas em Java.
Então, se SomeCollection precisa acessar BigClass, ele pode
static_cast<BigClass *>(this)
. Não há necessidade de um membro de dados extra ocupando espaço.fonte
BigClass
existe neste exemplo? Acho isso interessante, mas grita hackly na minha cara.Encontrei um ótimo aplicativo para herança privada, embora tenha um uso limitado.
Problema para resolver
Suponha que você receba a seguinte API C:
Agora, seu trabalho é implementar essa API usando C ++.
Abordagem C-ish
É claro que poderíamos escolher um estilo de implementação C-ish como este:
Mas existem várias desvantagens:
struct
erradostruct
Abordagem C ++
Podemos usar C ++, então por que não usar todos os seus poderes?
Apresentando o gerenciamento automatizado de recursos
Os problemas acima estão basicamente todos ligados ao gerenciamento manual de recursos. A solução que vem à mente é herdar
Widget
e adicionar uma instância de gerenciamento de recursos à classe derivadaWidgetImpl
para cada variável:Isso simplifica a implementação para o seguinte:
Assim, resolvemos todos os problemas acima. Mas um cliente ainda pode esquecer os setters de
WidgetImpl
e atribuir aoWidget
membros diretamente.Herança privada entra em cena
Para encapsular os
Widget
membros, usamos herança privada. Infelizmente, agora precisamos de duas funções extras para lançar entre as duas classes:Isso torna as seguintes adaptações necessárias:
Esta solução resolve todos os problemas. Sem gerenciamento manual de memória e
Widget
é bem encapsulado, de modo queWidgetImpl
não possui mais membros de dados públicos. Isso torna a implementação fácil de usar corretamente e difícil (impossível?) De usar incorretamente.Os trechos de código formam um exemplo de compilação no Coliru .
fonte
Se a classe derivada - precisa reutilizar o código e - você não pode alterar a classe base e - está protegendo seus métodos usando os membros da base sob um bloqueio.
então você deve usar herança privada, caso contrário, você corre o risco de métodos de base desbloqueados exportados por meio desta classe derivada.
fonte
Às vezes, pode ser uma alternativa à agregação , por exemplo, se você deseja agregação, mas com o comportamento alterado da entidade agregável (substituindo as funções virtuais).
Mas você está certo, não há muitos exemplos do mundo real.
fonte
Herança privada a ser usada quando a relação não é "é uma", mas a nova classe pode ser "implementada em termos de classe existente" ou a nova classe "funciona como" uma classe existente.
exemplo de "padrões de codificação C ++ por Andrei Alexandrescu, Herb Sutter": - Considere que duas classes Square e Rectangle cada uma tem funções virtuais para definir sua altura e largura. Então Square não pode herdar corretamente de Rectangle, porque o código que usa um Rectangle modificável assumirá que SetWidth não altera a altura (se Rectangle documenta explicitamente esse contrato ou não), enquanto Square :: SetWidth não pode preservar esse contrato e sua própria quadratura invariante em o mesmo tempo. Mas Rectangle também não pode herdar corretamente de Square, se clientes de Square assumem, por exemplo, que a área de um Square é sua largura ao quadrado, ou se eles contam com alguma outra propriedade que não vale para Rectangles.
Um quadrado "é um" retângulo (matematicamente), mas um quadrado não é um retângulo (comportamentalmente). Consequentemente, em vez de "é-um", preferimos dizer "funciona-como-a" (ou, se preferir, "utilizável-como-a") para tornar a descrição menos sujeita a mal-entendidos.
fonte
Uma classe contém um invariante. O invariante é estabelecido pelo construtor. No entanto, em muitas situações é útil ter uma visão do estado de representação do objeto (que você pode transmitir pela rede ou salvar em um arquivo - DTO se preferir). REST é melhor executado em termos de um AggregateType. Isso é especialmente verdadeiro se você estiver constantemente correto. Considerar:
Neste ponto, você pode simplesmente armazenar coleções de cache em contêineres e procurá-las na construção. Útil se houver algum processamento real. Observe que o cache faz parte do QE: as operações definidas no QE podem significar que o cache é parcialmente reutilizável (por exemplo, c não afeta a soma); no entanto, quando não há cache, vale a pena dar uma olhada.
A herança privada quase sempre pode ser modelada por um membro (armazenando referência à base, se necessário). Nem sempre vale a pena modelar dessa forma; às vezes, a herança é a representação mais eficiente.
fonte
Se você precisar de um
std::ostream
com algumas pequenas mudanças (como nesta questão ), pode ser necessárioMyStreambuf
que derivestd::streambuf
e implemente as mudanças láMyOStream
que derivastd::ostream
disso também inicializa e gerencia uma instância deMyStreambuf
e passa o ponteiro para essa instância para o construtor destd::ostream
A primeira ideia pode ser adicionar a
MyStream
instância como um membro de dados àMyOStream
classe:Mas as classes básicas são construídas antes de quaisquer membros de dados, portanto, você está passando um ponteiro para uma
std::streambuf
instância ainda não construída para astd::ostream
qual é um comportamento indefinido.A solução é proposta na resposta de Ben à pergunta acima mencionada , simplesmente herde do buffer de fluxo primeiro, depois do fluxo e, em seguida, inicialize o fluxo com
this
:No entanto, a classe resultante também pode ser usada como uma
std::streambuf
instância que geralmente é indesejada. Mudar para herança privada resolve este problema:fonte
Só porque C ++ tem um recurso, não significa que seja útil ou que deva ser usado.
Eu diria que você não deveria usá-lo.
Se você estiver usando de qualquer maneira, bem, você está basicamente violando o encapsulamento e diminuindo a coesão. Você está colocando dados em uma classe e adicionando métodos que manipulam os dados em outra.
Como outros recursos do C ++, ele pode ser usado para obter efeitos colaterais, como selar uma classe (conforme mencionado na resposta de dribeas), mas isso não o torna um bom recurso.
fonte