Forçar todas as classes a implementar/substituir um método 'virtual puro' na hierarquia de herança multinível
-
13-11-2019 - |
Pergunta
Em C++ por que o puro virtual
método exige sua substituição compulsória apenas aos seus filhos imediatos (para criação de objetos), mas não aos netos e assim por diante?
struct B {
virtual void foo () = 0;
};
struct D : B {
virtual void foo () { ... };
};
struct DD : D {
// ok! ... if 'B::foo' is not overridden; it will use 'D::foo' implicitly
};
Não vejo grande problema em deixar esse recurso de fora.
Por exemplo, do ponto de vista do design da linguagem, poderia ter sido possível que, struct DD
é permitido usar D::foo
somente se tiver alguma declaração explícita como using D::foo;
.Caso contrário, terá que substituir foo
obrigatório.
Existe alguma maneira prática de ter esse efeito em C++?
Solução
Encontrei um mecanismo onde pelo menos somos solicitados a anunciar o método substituído explicitamente.Não é o caminho perfeito, mas pelo menos um pouco próximo.
Suponha que temos poucos puros virtual
métodos em class B
(base):
class B {
virtual void foo () = 0;
virtual void bar (int) = 0;
};
Entre eles, quero foo()
ser substituído por toda a hierarquia, então abstrato foo()
1 nível acima para simplicidade:
class Register_foo {
template<typename T> // this matches the signature of 'foo'
Register_foo (void (T::*)()) {}
};
class Base : public virtual Register_foo { // <---- virtual inheritance
virtual void bar (int) = 0;
Base () : Register_foo(&Base::foo) {} // <--- explicitly pass the function name
};
Cada classe filha subsequente na hierarquia teria que registro isso é foo
dentro de seu cada construtor explicitamente.por exemplo.:
struct D : B {
D () : Register_foo(&D::foo) {}
D (const D &other) : Register_foo(&D::foo) {}
virtual void foo () {};
};
Este mecanismo de registro não tem nada a ver com a lógica de negócios.Mas pelo menos dentro do construtor de qualquer classe filha seria mencionado que, o que foo
está usando.
Embora, a criança class
pode optar por se registrar usando seu próprio foo
ou de seus pais foo
, mas pelo menos é isso anunciado explicitamente.
Outras dicas
O que você está basicamente pedindo é exigir que a classe mais derivada implemente o functiom.E minha pergunta é:por que?Sobre a única vez que posso imaginar que isso seja relevante é uma função como clone()
ouanother()
, que retorna uma nova instância do mesmo tipo.E é isso que você realmente deseja aplicar, que a nova instância tem o mesmo tipo;Mesmo lá, onde a função é realmente implementada é irrelevante.E você pode impor isso:
class Base
{
virtual Base* doClone() const = 0;
public:
Base* clone() const
{
Base* results = doClone();
assert( typeid(*results) == typeid(*this) );
return results;
}
}
(Na prática, nunca encontrei pessoas esquecendo de substituir clone
Para ser um problema real, então nunca me preocupei com algo como o acima.É uma técnica geralmente útil, no entanto, sempre que você quiser aplicar pós-condições.)
Um virtual puro significa que, para ser instanciado, o virtual puro deve ser substituído em alguns descendente da classe que declara a função virtual pura.Isso pode estar na classe que está sendo instanciada ou em qualquer classe intermediária entre a base que declara o virtual puro e aquela que está sendo instanciada.
Ainda é possível, entretanto, ter classes intermediárias que derivam de uma com um virtual puro sem substituir esse virtual puro.Assim como a classe que declara o virtual puro, essas classes só podem ser usadas como classes baseadas;você não pode criar instâncias dessas classes, apenas de classes que derivam delas, nas quais todo virtual puro foi implementado.
No que diz respeito a exigir que um descendente substitua um virtual, mesmo que uma classe intermediária já tenha feito isso, a resposta é não, o C++ não fornece nada que pelo menos tenha a intenção de fazer isso.Quase parece que você pode hackear algo usando herança múltipla (provavelmente virtual), para que a implementação na classe intermediária esteja presente, mas tentar usá-la seria ambíguo, mas não pensei nisso o suficiente para ter certeza como (ou se) funcionaria - e mesmo que funcionasse, só funcionaria ao tentar chamar a função em questão, e não apenas instanciar um objeto.
No seu exemplo, você não declarou D::foo
puro;é por isso que não precisa ser substituído.Se você quiser exigir que ele seja substituído novamente, declare-o puro.
Se você quiser ser capaz de instanciar D
, mas força qualquer outra classe derivada a substituir foo
, então você não pode.No entanto, você poderia derivar ainda outra classe de D
que o redeclara puro e, em seguida, classes derivadas de que deve substituí-lo novamente.
Existe alguma maneira prática de ter esse efeito em C++?
Não, e por um bom motivo.Imagine a manutenção em um grande projeto se isso fizesse parte da norma.Alguma classe base ou classe base intermediária precisa adicionar alguma interface pública, uma interface abstrata.Agora, cada filho e neto precisaria ser alterado e recompilado (mesmo que fosse tão simples quanto adicionar usando D::foo() como você sugeriu), você provavelmente verá para onde isso está indo, cozinha do inferno.
Se você realmente deseja impor a implementação, pode forçar a implementação de algum outro virtual puro na(s) classe(s) filha(s).Isso também pode ser feito usando o padrão CRTP.