Sobreescribir funciones virtuales públicas con funciones privadas en C++


¿Hay alguna razón para hacer que los permisos en una función virtual de C++ sobrescrita sean diferentes de la clase base? ¿Hay algún peligro en 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, basado en la suposición de que no es posible anular una función miembro privada. Sin embargo, Este article muestra un ejemplo de sobreescritura de funciones privadas. Por supuesto otra parte de las preguntas frecuentes de C++ 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 clases derivadas vs clase base?

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

Author: curiousguy, 2009-01-27

7 answers

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 se pueden 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, a pesar de que un objeto Derivado "es-un" Objeto Base, algo que debería ser capaz de hacer a un objeto de clase Base no se puede hacer a un objeto de clase Derivada, rompiendo la Sustitución de Liskov Prinicple

¿Causará esto un problema "técnico" en su programa? Tal vez no. Pero probablemente significará que el objeto de sus clases no se comportará de una 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 al que se hace referencia en otra respuesta), es probable que tenga un modelo de herencia donde la herencia no está modelando realmente "is-a" (por ejemplo, el ejemplo de Scott Myers Rectángulo, pero no se puede cambiar el ancho de un Cuadrado independiente de su altura como se puede para un rectángulo) y es posible que deba reconsiderar sus relaciones de clase.

 29
Author: JohnMcG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-21 21:40:54

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 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 hecho de que esa situación aparezca sugeriría un mal diseño de OO en algún lugar de la línea que probablemente debería ser refactorizado.

 39
Author: Eclipse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 18:27:21

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 un puntero base o derivado.

Esto en mi opinión sería raro y confuso.

 6
Author: Andrew Grant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 18:26:46

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

 4
Author: Nemanja Trifunovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 19:17:44

Se puede hacer, y muy ocasionalmente dará lugar a 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 desalentamos 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. Por lo tanto, hicimos que la función dada sea privada en la clase derivada para ayudar a todos a recordar no usarla si puede evitarlo. No elimina la capacidad de usarlo, pero detectará algunos usos cuando el código intente compilar, en lugar de más tarde en las revisiones de código.

 3
Author: Caleb Huitt - cjhuitt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 18:41:57
  1. No hay problema técnico, si se refiere a técnico, ya que hay un costo de tiempo de ejecución oculto.
  2. Si heredas 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.
 1
Author: MSN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-03 14:57:56

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 quieren la funcionalidad padre y algunos quieren la funcionalidad hijo:

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 padre. Mientras que el Seal contiene una referencia al hijo y usa la funcionalidad única del hijo e ignora el padre. En este ejemplo, el Seal no debería llamar Animate. Ahora, como se mencionó anteriormente, Animate se puede llamar al objeto base, pero eso es más difícil y generalmente no se debe hacer.

 1
Author: hubatish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-17 23:55:45