2009-01-27 19 views
37

¿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?Anulación de las funciones virtuales públicas con funciones privadas en C++

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 definitiva, basándose en la suposición de que no es posible anular una función de miembro privada. Sin embargo, This article muestra un ejemplo de funciones privadas primordiales. Por supuesto, another part of the C++ faq recomienda no hacerlo.

Mis preguntas concretas:

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

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

+3

reinventando "protegido" ¿o sí? –

Respuesta

27

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 crea algo que la Base ha declarado como pública privada, le está quitando algo. Ahora, aunque un objeto derivado sea un objeto base, es algo que debería poder hacer. a un objeto de clase Base que no puede hacer a un objeto de clase Derivado, rompiendo el Liskov Substitution Prinicple

¿Esto causará un problema "técnico" en su programa? Quizás no. Pero probablemente signifique que el objeto de sus clases no se comportará en una forma en que los usuarios esperan que se comporten.

Si se encuentra en la situación en que esto es lo que desea (excepto en el caso de un método desaprobado al que se hace referencia en otra respuesta), posibilidad s tiene un modelo de herencia en el que la herencia realmente no está modelando "is-a" (p. El ejemplo de Scott Myers Square 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.

6

No hay ningún problema técnico, pero el resultado final será con una situación en la que las funciones disponibles públicamente dependerán de si usted tiene una base o puntero derivada.

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

35

Obtiene el sorprendente resultado de que si tiene un hijo, no puede llamar a foo, pero puede convertirlo en 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 podría ser capaz de idear un ejemplo en el que no desea una función expuesta, excepto cuando se está tratando 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.

+0

Qt lo hizo y me trajo aquí :) Gracias. – mlvljr

+3

También: como un IDE, ¡eres una mierda! – mlvljr

+21

@mlvljr: ¡Pero como un evento astronómico, soy bastante increíble! – Eclipse

3

Se puede hacer, y muy ocasionalmente dará lugar a 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 compilar, en lugar de hacerlo más tarde en las revisiones del código.

1
  1. No hay problema técnico, si se refiere por técnico, ya que hay un costo de tiempo de ejecución oculto.
  2. Si hereda la base públicamente, no debe 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.
4

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

1

Un buen uso de 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 de los padres y algunos quieren la funcionalidad niño:

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 usa la funcionalidad de los padres. Mientras que el Seal contiene una referencia al elemento secundario y utiliza la funcionalidad secundaria única e ignora la del elemento principal. En este ejemplo, el Seal no debe llamar al Animate. Ahora, como se mencionó anteriormente, se puede invocar Animate mediante conversión al objeto base, pero eso es más difícil y generalmente no debería hacerse.

+0

Interfaz de escucha/observador, recepción de devoluciones de llamada. – dashesy