Pregunta

¿Hay alguna razón para hacer que los permisos en una función virtual anulada de C ++ 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 C ++ faq 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 final, basándose en el supuesto 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 las preguntas frecuentes de C ++ recomienda en contra de hacerlo.

Mis preguntas concretas:

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

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

¿Fue útil?

Solución

El problema es que los métodos de la clase Base son su forma de declarar su interfaz. Es, en esencia, decir, & "; Estas son las cosas que puede hacer a los objetos de esta clase. &";

Cuando en una clase Derivada haces algo que la Base había declarado como público privado, estás quitando algo. Ahora, aunque un objeto Derivado & Quot; es-a & Quot; Objeto base, algo que debería poder hacer con un objeto de clase Base que no puede hacer con un objeto de clase derivada, rompiendo el Prinkple de sustitución de Liskov

¿Esto causará un & "; técnico &"; problema en tu programa? Tal vez no. Pero probablemente significará que el objeto de sus clases no se comportará de la manera que sus usuarios esperan que se comporten.

Si se encuentra en la situación en la que esto es lo que desea (excepto en el caso de un método obsoleto mencionado en otra respuesta), es probable que tenga un modelo de herencia en el que la herencia no sea realmente un modelo " ; es-a, " (por ejemplo, el cuadrado de Scott Myers que hereda de un rectángulo, pero no puede cambiar el ancho de un cuadrado independientemente de su altura como puede hacerlo para un rectángulo) y es posible que deba reconsiderar sus relaciones de clase.

Otros consejos

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<base *>(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 esté tratando como una instancia de la clase base. Pero el hecho de que surja esa situación sugeriría un mal diseño de OO en algún lugar a lo largo de la línea que probablemente debería ser refactorizado.

No hay ningún problema técnico, pero terminará en 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 está utilizando una herencia privada, es decir, si 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 usando una biblioteca que contiene una clase con una función pública que solíamos usar, pero ahora desaconsejamos el uso debido a otros problemas potenciales (hay métodos más seguros para llamar). También tenemos una clase derivada de esa clase que gran parte de nuestro código usa directamente. Entonces, hicimos 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 capturará algunos usos cuando el código intente compilar, en lugar de más adelante en las revisiones de código.

  1. No hay problema técnico, si te refieres a técnico porque 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 protección o privacidad, 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 de escucha / observador.

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 desean la funcionalidad principal y otros desean la funcionalidad secundaria:

class AnimationList {
   public:
     void AnimateAll() {
       for (auto & animatable : animatables) {
         // Uses the parent functionality.
         animatable->Animate();
       }
     }
   private:
     vector<AnimatableListener*> animatables;
};

class Seal {
  public:
    void Dance() {
      // Uses unique functionality.
      ball->TossUp();
    }
  private:
    BouncyBall* ball;
};

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top