puro - que es un objeto en c++
¿Cuál es el punto de una función virtual final? (10)
Wikipedia tiene el siguiente ejemplo sobre el modificador final de C ++ 11:
struct Base2 {
virtual void f() final;
};
struct Derived2 : Base2 {
void f(); // ill-formed because the virtual function Base2::f has been marked final
};
No entiendo el objetivo de introducir una función virtual e inmediatamente marcarla como definitiva. ¿Es simplemente un mal ejemplo o hay más?
No entiendo el objetivo de introducir una función virtual e inmediatamente marcarla como definitiva.
El propósito de ese ejemplo es ilustrar cómo funciona el final
, y simplemente lo hace.
Un propósito práctico podría ser ver cómo un vtable influye en el tamaño de una clase.
struct Base2 {
virtual void f() final;
};
struct Base1 {
};
assert(sizeof(Base2) != sizeof(Base1)); //probably
Base2
simplemente se puede usar para probar los detalles de la plataforma, y no tiene sentido anular f()
ya que está allí solo para fines de prueba, por lo que se marca como final
. Por supuesto, si estás haciendo esto, hay algo mal en el diseño. Personalmente, no crearía una clase con una función virtual
solo para verificar el tamaño del vfptr
.
Agregando a las buenas respuestas anteriores - Aquí hay una aplicación conocida de final (muy inspirada en Java). Supongamos que definimos una función wait () en una clase base, y queremos solo una implementación de wait () en todos sus descendientes. En este caso, podemos declarar wait () como final.
Por ejemplo:
class Base {
public:
virtual void wait() final { cout << "I m inside Base::wait()" << endl; }
void wait_non_final() { cout << "I m inside Base::wait_non_final()" << endl; }
};
y aquí está la definición de la clase derivada:
class Derived : public Base {
public:
// assume programmer had no idea there is a function Base::wait()
// error: wait is final
void wait() { cout << "I am inside Derived::wait() /n"; }
// that''s ok
void wait_non_final() { cout << "I am inside Derived::wait_non_final(); }
}
Sería inútil (y no correcto) si wait () fuera una función virtual pura . En este caso: el compilador le pedirá que defina wait () dentro de la clase derivada. Si lo hace, le dará un error porque wait () es final.
¿Por qué una función final debe ser virtual? (que también es confuso) Porque (imo) 1) el concepto de final está muy cerca del concepto de funciones virtuales [las funciones virtuales tienen muchas implementaciones - las funciones finales tienen una sola implementación], 2) es fácil implementar el efecto final usando vtables.
Al refaccionar el código heredado (por ejemplo, eliminar un método que es virtual de una clase madre), esto es útil para garantizar que ninguna de las clases secundarias esté utilizando esta función virtual.
// Removing foo method is not impacting any child class => this compiles
struct NoImpact { virtual void foo() final {} };
struct OK : NoImpact {};
// Removing foo method is impacting a child class => NOK class does not compile
struct ImpactChildClass { virtual void foo() final {} };
struct NOK : ImpactChildClass { void foo() {} };
int main() {}
En lugar de esto:
public:
virtual void f();
Me parece útil escribir esto:
public:
virtual void f() final
{
do_f(); // breakpoint here
}
protected:
virtual void do_f();
La razón principal es que ahora tiene un único lugar para el punto de interrupción antes de enviarlo a cualquiera de las posibles implementaciones anuladas. Tristemente (en mi humilde opinión), decir "final" también requiere que digas "virtual".
Encontré otro caso donde la función virtual es útil para declarar como final. Este caso es parte de la lista de advertencias de SonarQube . La descripción de advertencia dice:
Llamar a una función de miembro anulable desde un constructor o destructor podría provocar un comportamiento inesperado al crear una instancia de una subclase que anula la función de miembro.
Por ejemplo:
- Por contrato, el constructor de la clase de subclase comienza llamando al constructor de la clase padre.
- El constructor de la clase padre llama a la función miembro principal y no a la sobrescrita en la clase secundaria, lo cual es confuso para el desarrollador de la clase hija.
- Puede producir un comportamiento indefinido si la función miembro es puramente virtual en la clase padre.
Ejemplo de código no conforme
class Parent {
public:
Parent() {
method1();
method2(); // Noncompliant; confusing because Parent::method2() will always been called even if the method is overridden
}
virtual ~Parent() {
method3(); // Noncompliant; undefined behavior (ex: throws a "pure virtual method called" exception)
}
protected:
void method1() { /*...*/ }
virtual void method2() { /*...*/ }
virtual void method3() = 0; // pure virtual
};
class Child : public Parent {
public:
Child() { // leads to a call to Parent::method2(), not Child::method2()
}
virtual ~Child() {
method3(); // Noncompliant; Child::method3() will always be called even if a child class overrides method3
}
protected:
void method2() override { /*...*/ }
void method3() override { /*...*/ }
};
Solución compatible
class Parent {
public:
Parent() {
method1();
Parent::method2(); // acceptable but poor design
}
virtual ~Parent() {
// call to pure virtual function removed
}
protected:
void method1() { /*...*/ }
virtual void method2() { /*...*/ }
virtual void method3() = 0;
};
class Child : public Parent {
public:
Child() {
}
virtual ~Child() {
method3(); // method3() is now final so this is okay
}
protected:
void method2() override { /*...*/ }
void method3() final { /*...*/ } // this virtual function is "final"
};
Esta es la razón por la que en realidad podría elegir declarar una función tanto virtual
como final
en una clase base:
class A {
void f();
};
class B : public A {
void f(); // Compiles fine!
};
class C {
virtual void f() final;
};
class D : public C {
void f(); // Generates error.
};
Una función marcada como final
debe ser también virtual
. Marcar una función final
impide declarar una función con el mismo nombre y firma en una clase derivada.
No me parece útil en absoluto. Creo que esto fue solo un ejemplo para demostrar la sintaxis.
Un posible uso es si no desea que f sea realmente anulable, pero aún desea generar un vtable, pero esa sigue siendo una forma horrible de hacer las cosas.
Para que una función se etiquete como final
, debe ser virtual
, es decir, en C ++ 11 §10.3 párrafo. 2:
[...] Por conveniencia, decimos que cualquier función virtual se anula.
y para 4:
Si una función virtual f en alguna clase B está marcada con el especificador de virtudes final y en una clase D derivada de B una función D :: f prevalece sobre B :: f, el programa está mal formado. [...]
es decir, se requiere que final
se use con funciones virtuales (o con clases para bloquear herencia) solamente. Por lo tanto, el ejemplo requiere que se use virtual
para que sea un código válido de C ++.
EDITAR: Para ser totalmente claro: el "punto" preguntó sobre las preocupaciones por las que incluso se usa virtual. La razón fundamental por la que se usa es (i) porque el código no se compilaría de otra manera, y, (ii) ¿por qué complicar el ejemplo utilizando más clases cuando una es suficiente? Por lo tanto, se usa exactamente una clase con una función final virtual como ejemplo.
Típicamente final
no se usará en la definición de la clase base de una función virtual. final
será utilizado por una clase derivada que anula la función para evitar que otros tipos derivados anulen más la función. Como la función principal debe ser virtual normalmente, significa que cualquiera puede anular esa función en otro tipo derivado. final
permite especificar una función que anula a otra pero que no puede anularse.
Por ejemplo, si está diseñando una jerarquía de clases y necesita anular una función, pero no desea permitir que los usuarios de la jerarquía de clases hagan lo mismo, entonces puede marcar las funciones como finales en sus clases derivadas.
virtual
+ final
se utilizan en una declaración de función para hacer que el ejemplo sea corto.
Con respecto a la sintaxis de virtual
y final
, el ejemplo de Wikipedia sería más expresivo al introducir struct Base2 : Base1
con Base1 que contiene virtual void f();
y Base2 que contiene el void f() final;
(vea abajo).
Estándar
En referencia a N3690 :
-
virtual
comofunction-specifier
puede ser parte dedecl-specifier-seq
-
final
puede ser parte devirt-specifier-seq
No hay ninguna regla que tenga que usar la palabra clave virtual
y los Identificadores con un significado especial final
juntos. Sec 8.4, definiciones de función (opción de atención = opcional):
función-definición:
atributo-especificador-seq (opt) decl-specifier-seq (opt) declarador virt-specifier-seq (opt) función-cuerpo
Práctica
Con C ++ 11, puede omitir la palabra clave virtual
cuando usa final
. Esto se compila en gcc> 4.7.1, en clang> 3.0 con C ++ 11, en msvc, ... (ver compilador explorador ).
struct A
{
virtual void f() {}
};
struct B : A
{
void f() final {}
};
int main()
{
auto b = B();
b.f();
}
PD: El ejemplo de cppreference tampoco usa virtual junto con final en la misma declaración.
PPS: lo mismo se aplica para override
.