Anulación de funciones virtuales públicas con funciones privadas en C ++

¿Hay alguna razón para hacer que los permisos en una función virtual C ++ anulada sean diferentes de la clase base? ¿Hay algún peligro al hacerlo?

Por ejemplo:

class base { public: virtual int foo(double) = 0; } class child : public base { private: virtual int foo(double); } 

El FAQ de C ++ dice que es una mala idea, pero no dice por qué.

He visto este modismo en algún código y creo que el autor estaba tratando de hacer que la clase sea definitiva, en base a la suposición de que no es posible anular una función de miembro privado. Sin embargo, este artículo muestra un ejemplo de anulación de funciones privadas. Por supuesto, otra parte de C ++ faq recomienda no hacerlo.

Mis preguntas concretas:

  1. ¿Hay algún problema técnico con el uso de un permiso diferente para los métodos virtuales en las clases derivadas frente a la clase base?

  2. ¿Hay alguna razón legítima para hacerlo?

El problema es que los métodos de la clase Base son su manera de declarar su interfaz. En esencia, dice: “Estas son las cosas que puedes hacer a los objetos de esta clase”.

Cuando en una clase derivada creas algo que la Base había declarado como público privado, te estás quitando algo. Ahora, a pesar de que un Objeto derivado es “un objeto Base”, algo que usted debería poder hacer a un objeto de la clase Base que no puede hacer a un objeto de clase Derivado, rompiendo el Principio de Sustitución de Liskov

¿Esto causará un problema “técnico” en su progtwig? Tal vez no. Pero probablemente significará que el objeto de sus clases no se comportará de la manera que sus usuarios esperan que se comporte.

Si se encuentra en la situación en que esto es lo que quiere (excepto en el caso de un método desaprobado al que se hace referencia en otra respuesta), es probable que tenga un modelo de herencia en el que la herencia realmente no esté modelando “is-a” ( por ejemplo, Square de ejemplo de Scott Myers hereda de Rectangle, pero no puedes cambiar el ancho de un Square independientemente de su altura como lo haces con un rectángulo) y es posible que tengas que reconsiderar tus relaciones de clase.

Obtienes el sorprendente resultado de que si tienes un hijo, no puedes llamar a foo, pero puedes lanzarlo a una base y luego llamar a foo.

 child *c = new child(); c->foo; // compile error (can't access private member) static_cast(c)->foo(); // this is fine, but still calls the implementation in child 

Supongo que es posible que pueda idear un ejemplo en el que no desee que se exponga una función, excepto cuando la trate como una instancia de la clase base. Pero el solo hecho de que esa situación aparezca sugiere un mal diseño de OO en algún lugar a lo largo de la línea que probablemente debería refactorizarse.

No hay ningún problema técnico, pero terminará con una situación en la que las funciones disponibles públicamente dependerán de si tiene una base o un puntero derivado.

Esto en mi opinión sería extraño y confuso.

Puede ser muy útil si usa herencia privada, es decir, desea reutilizar una funcionalidad (personalizada) de una clase base, pero no la interfaz.

Se puede hacer y muy ocasionalmente generará beneficios. Por ejemplo, en nuestra base de código, estamos utilizando una biblioteca que contiene una clase con una función pública que solíamos usar, pero ahora desaconsejamos su uso debido a otros problemas potenciales (hay métodos más seguros para llamar). También tenemos una clase derivada de esa clase que muchos de nuestros códigos usan directamente. Por lo tanto, hemos hecho que la función dada sea privada en la clase derivada para ayudar a todos a recordar no usarla si pueden ayudarla. No elimina la capacidad de usarlo, pero tendrá algunos usos cuando el código intente comstackr, en lugar de hacerlo más tarde en las revisiones del código.

  1. Ningún problema técnico, si se refiere por técnico, ya que hay un costo de tiempo de ejecución oculto.
  2. Si heredas la base públicamente, no deberías hacer esto. Si hereda a través de protegido o privado, esto puede ayudar a evitar el uso de métodos que no tienen sentido a menos que tenga un puntero base.

Un buen caso de uso para la herencia privada es una interfaz de evento Listener / Observer.

Código de ejemplo para el objeto privado:

 class AnimatableListener { public: virtual void Animate(float delta_time); }; class BouncyBall : public AnimatableListener { public: void TossUp() {} private: void Animate(float delta_time) override { } }; 

Algunos usuarios del objeto quieren la funcionalidad principal y otros quieren la funcionalidad secundaria:

 class AnimationList { public: void AnimateAll() { for (auto & animatable : animatables) { // Uses the parent functionality. animatable->Animate(); } } private: vector animatables; }; class Seal { public: void Dance() { // Uses unique functionality. ball->TossUp(); } private: BouncyBall* ball; }; 

De esta forma, AnimationList puede contener una referencia a los padres y usa la funcionalidad principal. Mientras que el Seal contiene una referencia al niño y usa la funcionalidad infantil única e ignora la del padre. En este ejemplo, el Seal no debe llamar a Animate . Ahora, como se mencionó anteriormente, se puede Animate lanzando al objeto base, pero eso es más difícil y generalmente no debería hacerse.