Forçar todas as classes a implementar/substituir um método 'virtual puro' na hierarquia de herança multinível

StackOverflow https://stackoverflow.com/questions/9477581

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++?

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top