¿Cuáles son las diferencias entre reemplazar funciones virtuales y ocultar funciones no virtuales?

Dado el siguiente fragmento de código, ¿cuáles son las diferencias en las llamadas de función? ¿Qué es la función escondida? ¿Qué es la función anulando? ¿Cómo se relacionan con las sobrecargas de funciones? ¿Cuál es la diferencia entre los dos? No pude encontrar una buena descripción de estos en un solo lugar, así que estoy preguntando aquí para poder consolidar la información.

class Parent { public: void doA() { cout << "doA in Parent" << endl; } virtual void doB() { cout << "doB in Parent" << endl; } }; class Child : public Parent { public: void doA() { cout << "doA in Child" << endl; } void doB() { cout << "doB in Child" <doA(); p2->doA(); cp->doA(); p1->doB(); p2->doB(); cp->doB(); } 

¿Qué es la función escondida?

… es una forma de ocultar el nombre. Un simple ejemplo:

 void foo(int); namespace X { void foo(); void bar() { foo(42); // will not find `::foo` // because `X::foo` hides it } } 

Esto también se aplica a la búsqueda de nombre en una clase base:

 class Base { public: void foo(int); }; class Derived : public Base { public: void foo(); void bar() { foo(42); // will not find `Base::foo` // because `Derived::foo` hides it } }; 

¿Qué es la función anulando?

Esto está relacionado con el concepto de funciones virtuales. [class.virtual] / 2

Si una función miembro virtual vf se declara en una clase Base y en una clase Derived , derivada directa o indirectamente de Base , una función miembro vf con el mismo nombre, parameter-type-list, cv-qualification y ref-qualifier (o ausencia de la misma) como Base::vf se declara, luego Derived::vf también es virtual (esté o no declarado) y anula Base::vf .

 class Base { private: virtual void vf(int) const &&; virtual void vf2(int); virtual Base* vf3(int); }; class Derived : public Base { public: // accessibility doesn't matter! void vf(int) const &&; // overrides `Base::vf(int) const &&` void vf2(/*int*/); // does NOT override `Base::vf2` Derived* vf3(int); // DOES override `Base::vf3` (covariant return type) }; 

El overrider final se vuelve relevante cuando se llama a una función virtual: [class.virtual] / 2

Una función de miembro virtual C::vf de un objeto de clase S es una sobrescritura final a menos que la clase más derivada de la cual S es un subobjeto de clase base (si lo hay) declare o herede otra función de miembro que invalide vf .

Es decir, si tiene un objeto de tipo S , el overrider final es el primer overrider que ve al atravesar la jerarquía de clases de S hasta sus clases base. El punto importante es que el tipo dynamic de la expresión de llamada a función se usa para determinar el rebase final:

 Base* p = new Derived; p -> vf(); // dynamic type of `*p` is `Derived` Base& b = *p; b . vf(); // dynamic type of `b` is `Derived` 

¿Cuál es la diferencia entre anular y ocultar?

Esencialmente, las funciones en la clase base siempre están ocultas por funciones del mismo nombre en una clase derivada; no importa si la función en la clase derivada anula o no la función virtual de una clase base:

 class Base { private: virtual void vf(int); virtual void vf2(int); }; class Derived : public Base { public: void vf(); // doesn't override, but hides `Base::vf(int)` void vf2(int); // overrides and hides `Base::vf2(int)` }; 

Para encontrar un nombre de función, se usa el tipo estático de una expresión:

 Derived d; d.vf(42); // `vf` is found as `Derived::vf()`, this call is ill-formed // (too many arguments) 

¿Cómo se relacionan con las sobrecargas de funciones?

Como “ocultación de funciones” es una forma de ocultar nombres, todas las sobrecargas se ven afectadas si el nombre de una función está oculto:

 class Base { private: virtual void vf(int); virtual void vf(double); }; class Derived : public Base { public: void vf(); // hides `Base::vf(int)` and `Base::vf(double)` }; 

Para anulación de función, solo se anulará la función en la clase base con los mismos argumentos; Por supuesto, puede sobrecargar una función virtual:

 class Base { private: virtual void vf(int); virtual void vf(double); void vf(char); // will be hidden by overrides in a derived class }; class Derived : public Base { public: void vf(int); // overrides `Base::vf(int)` void vf(double); // overrides `Base::vf(double)` }; 

La diferencia entre llamar a una función de miembro virtual y llamar a una función de miembro no virtual es que, por definición, en el primer caso la función de destino se elige de acuerdo con el tipo dynamic de la expresión de objeto utilizada en la llamada, mientras que en el segundo caso en que se usa el tipo estático .

Eso es todo al respecto. Su ejemplo ilustra claramente esta diferencia mediante p2->doA() y p2->doB() . El tipo estático de la expresión *p2 es Parent , mientras que el tipo dynamic de la misma expresión es Child . Es por p2->doA() que p2->doA() llama a Parent::doA y p2->doB() llama a Child::doB .

En contextos en los que esa diferencia importa, el ocultamiento de nombres no entra en la imagen en absoluto.

Un ejemplo mucho más fácil que difiere b / w de todos ellos.

 class Base { public: virtual int fcn(); }; class D1 : public Base { public: // D1 inherits the definition of Base::fcn() int fcn(int); // parameter list differs from fcn in Base virtual void f2(); // new virtual function that does not exist in Base }; class D2 : public D1 { public: int fcn(int); // nonvirtual function hides D1::fcn(int) int fcn(); // overrides virtual fcn from Base void f2(); // overrides virtual f2 from D1 } 

El código de ejemplo que está escrito en la pregunta esencialmente da la respuesta cuando lo ejecuta.

Llamar a una función no virtual utilizará la función de la misma clase que el tipo de puntero, independientemente de si el objeto se creó realmente como algún otro tipo derivado. Mientras que llamar a una función virtual utilizará la función del tipo de objeto asignado original, independientemente del tipo de puntero que esté utilizando.

Entonces, la salida de su progtwig en este caso será:

 doA in Parent doA in Parent doA in Child doB in Parent doB in Child doB in Child 

Comenzaremos con los fáciles.

p1 es un puntero principal, por lo que siempre llamará a las funciones miembro de Parent .

cp es un puntero a Child , por lo que siempre llamará a las funciones miembro de Child .

Ahora el más difícil. p2 es un puntero principal, pero apunta a un objeto de tipo Child , por lo que llamará a las funciones de Child siempre que la función Parent coincidente sea virtual o que la función solo exista en Child y no en Parent . En otras palabras, Child oculta Parent::doA() con su propia doA() , pero anula Parent::doB() . El ocultamiento de funciones a veces se considera una forma de sobrecarga de funciones, porque a una función con el mismo nombre se le da una implementación diferente. Debido a que la función de ocultación pertenece a una clase diferente a la función oculta, tiene una firma diferente, lo que deja en claro cuál usar.

La salida para testStuff() será

 doA in Parent doA in Parent doA in Child doB in Parent doB in Child doB in Child 

En cualquier caso, Parent::doA() y Parent::doB() se pueden llamar dentro de Child usando la resolución del nombre, independientemente de la función de “virtualidad”. La función

 void Child::doX() { doA(); doB(); Parent::doA(); Parent::doB(); cout << "doX in Child" << endl; } 

demuestra esto cuando es llamado por cp->doX() al generar

 doA in Child doB in Child doA in Parent doB in Parent doX in Child 

Además, cp->Parent::doA() llamará a la versión para doA() de doA() .

p2 no puede hacer referencia a doX() porque es un Parent* , y el Parent no sabe nada en el Child . Sin embargo, p2 puede transferirse a un Child* , ya que se inicializó como uno, y luego se puede usar para llamar a doX() .

Intereting Posts