Obliga a todas las clases a implementar / anular un método ‘virtual puro’ en la jerarquía de herencia de niveles múltiples

En C ++, ¿por qué el método virtual puro exige su anulación obligatoria solo para sus hijos inmediatos (para la creación de objetos), pero no para los nietos y demás?

 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 }; 

No veo ningún problema en dejar esta característica.
Por ejemplo, en el punto de vista del diseño del lenguaje, podría haber sido posible que a la struct DD se le permita usar D::foo si tiene alguna statement explícita como using D::foo; . De lo contrario, debe anular obligatoriamente.

¿Hay alguna forma práctica de tener este efecto en C ++?

Encontré un mecanismo, donde al menos se nos pide que anunciemos explícitamente el método reemplazado. No es la manera perfecta, pero al menos algo cerca.

Supongamos que tenemos pocos métodos virtual puros en la class B (base):

 class B { virtual void foo () = 0; virtual void bar (int) = 0; }; 

Entre ellos, quiero que foo() sea ​​anulado por toda la jerarquía, luego abstrae foo() 1 nivel arriba por simplicidad:

 class Register_foo { template // 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 clase secundaria posterior en la jerarquía tendría que registrar su foo dentro de cada constructor explícitamente . p.ej:

 struct D : B { D () : Register_foo(&D::foo) {} D (const D &other) : Register_foo(&D::foo) {} virtual void foo () {}; }; 

Este mecanismo de registro no tiene nada que ver con la lógica comercial. Pero al menos dentro del constructor de cualquier clase infantil se mencionaría eso, que es lo que está usando.
Sin embargo, la class niños puede elegir registrarse utilizando su propio foo o el foo sus padres, pero al menos eso se anuncia explícitamente.

Lo que básicamente estás pidiendo es exigir que la clase más derivada implemente la función. Y mi pregunta es: ¿por qué? La única vez que puedo imaginar que esto sea relevante es una función como clone() u another() , que devuelve una nueva instancia del mismo tipo. Y eso es lo que realmente quiere hacer cumplir, que la nueva instancia tiene el mismo tipo; incluso allí, donde la función realmente se implementa es irrelevante. Y puedes hacer cumplir eso:

 class Base { virtual Base* doClone() const = 0; public: Base* clone() const { Base* results = doClone(); assert( typeid(*results) == typeid(*this) ); return results; } } 

(En la práctica, nunca he encontrado personas que olviden anular el clone para que sea un problema real, por lo que nunca me he molestado en algo como lo anterior. Sin embargo, es una técnica generalmente útil siempre que quiera aplicar post-condiciones).

Un virtual puro significa que para ser instanciado, el virtual puro debe ser anulado en algún descendiente de la clase que declara la función virtual pura. Puede estar en la clase que se está instanciando o en cualquier clase intermedia entre la base que declara la virtual pura y la que se está creando una instancia.

Sin embargo, todavía es posible tener clases intermedias que derivan de una con una virtual pura sin anular esa virtual pura. Al igual que la clase que declara el virtual puro, esas clases solo pueden usarse como clases basadas; no puede crear instancias de esas clases, solo de las clases que se derivan de ellas, en las que se ha implementado cada virtual puro.

En cuanto a requerir que un descendiente anule una virtual, incluso si una clase intermedia ya lo ha hecho, la respuesta es no, C ++ no proporciona nada que al menos tenga la intención de hacer eso. Casi parece que es posible que puedas hackear algo usando herencia múltiple (probablemente virtual), por lo que la implementación en la clase intermedia estaría presente pero intentar usarla sería ambiguo, pero no he pensado lo suficiente como para estar seguro cómo (o si) funcionaría, e incluso si lo hiciera, solo haría su truco cuando intente llamar a la función en cuestión, no solo instanciar un objeto.

En su ejemplo, no ha declarado D::foo pure; es por eso que no necesita ser anulado. Si desea exigir que se anule de nuevo, declare que es puro.

Si desea poder instanciar D , pero forzar cualquier clase derivada adicional para reemplazar a foo , entonces no puede. Sin embargo, podría derivar otra clase de D que la declare pura, y luego las clases derivadas de eso deben anularla nuevamente.

¿Hay alguna forma práctica de tener este efecto en C ++?

No, y por una buena razón. Imagine el mantenimiento en un proyecto grande si esto fuera parte del estándar. Alguna clase base o clase base intermedia necesita agregar alguna interfaz pública, una interfaz abstracta. Ahora, cada uno de sus hijos y nietos deberían cambiar y volver a comstackrse (incluso si fuera tan simple como agregar usando D :: foo () como sugeriste), probablemente veas hacia dónde va esto, cocina de los infiernos.

Si realmente desea imponer la implementación, puede forzar la implementación de otro virtual puro en la (s) clase (s) secundaria (s). Esto también se puede hacer usando el patrón CRTP también.