java - sencillos - manual de programacion android pdf
Funciones virtuales en constructores, ¿por qué los idiomas son diferentes? (6)
En C ++, cuando se llama a una función virtual desde un constructor, no se comporta como una función virtual. Creo que todos los que se encontraron por primera vez se sorprendieron, pero pensándolo mejor, si el constructor derivado todavía no se ha ejecutado, el objeto aún no es un derivado, entonces, ¿cómo se puede invocar una función derivada? Las condiciones previas no han tenido la oportunidad de configurarse. Ejemplo:
class base {
public:
base()
{
std::cout << "foo is " << foo() << std::endl;
}
virtual int foo() { return 42; }
};
class derived : public base {
int* ptr_;
public:
derived(int i) : ptr_(new int(i*i)) { }
// The following cannot be called before derived::derived due to how C++ behaves,
// if it was possible... Kaboom!
virtual int foo() { return *ptr_; }
};
Es exactamente lo mismo para Java y .NET pero eligieron ir por el otro lado, ¿fue la única razón el principio de menos sorpresa ?
¿Cuál crees que es la elección correcta?
Funciones virtuales en constructores, ¿por qué los idiomas son diferentes?
Porque no hay un buen comportamiento. Encuentro que el comportamiento de C ++ tiene más sentido (ya que los c-tors de clase base se llaman primero, es lógico que deben llamar a las funciones virtuales de la clase base; después de todo, la clase derivada c-tor aún no se ha ejecutado, por lo que puede no haber configurado las condiciones previas correctas para la función virtual de clase derivada).
Pero a veces, cuando quiero usar las funciones virtuales para inicializar el estado (de modo que no importa que se las llame con el estado sin inicializar), el comportamiento C # / Java es más agradable.
Ambas formas pueden conducir a resultados inesperados. Su mejor opción es no llamar a una función virtual en su constructor en absoluto.
Creo que la forma C ++ tiene más sentido, pero genera problemas de expectativa cuando alguien revisa tu código. Si conoce esta situación, no debe colocar su código intencionalmente en esta situación para una depuración posterior.
Creo que C ++ ofrece la mejor semántica en términos de tener el comportamiento "más correcto" ... sin embargo, es más trabajo para el compilador y el código es definitivamente no intuitivo para alguien que lo lea más tarde.
Con el enfoque .NET, la función debe ser muy limitada para no depender de ningún estado de objeto derivado.
Delphi hace un buen uso de constructores virtuales en el marco de la GUI de VCL:
type
TComponent = class
public
constructor Create(AOwner: TComponent); virtual; // virtual constructor
end;
TMyEdit = class(TComponent)
public
constructor Create(AOwner: TComponent); override; // override virtual constructor
end;
TMyButton = class(TComponent)
public
constructor Create(AOwner: TComponent); override; // override virtual constructor
end;
TComponentClass = class of TComponent;
function CreateAComponent(ComponentClass: TComponentClass; AOwner: TComponent): TComponent;
begin
Result := ComponentClass.Create(AOwner);
end;
var
MyEdit: TMyEdit;
MyButton: TMyButton;
begin
MyEdit := CreateAComponent(TMyEdit, Form) as TMyEdit;
MyButton := CreateAComponent(TMyButton, Form) as TMyButton;
end;
Hay una diferencia fundamental en cómo los idiomas definen el tiempo de vida de un objeto. En Java y .Net, los miembros del objeto son inicializados en cero / nulo antes de ejecutar cualquier constructor y es en este punto que comienza el tiempo de vida del objeto. Entonces, cuando ingresas al constructor, ya tienes un objeto inicializado.
En C ++, el tiempo de vida del objeto solo comienza cuando el constructor finaliza (aunque las variables miembro y las clases base están completamente construidas antes de comenzar). Esto explica el comportamiento cuando se llaman funciones virtuales y también por qué el destructor no se ejecuta si hay una excepción en el cuerpo del constructor.
El problema con la definición de Java / .Net de la duración del objeto es que es más difícil asegurarse de que el objeto siempre cumpla con su invariante sin tener que poner en casos especiales para cuando el objeto se inicializa pero el constructor no se ha ejecutado. El problema con la definición de C ++ es que tienes este período impar donde el objeto está en el limbo y no está completamente construido.
He encontrado el comportamiento C ++ muy molesto. No puede escribir funciones virtuales para, por ejemplo, devolver el tamaño deseado del objeto y hacer que el constructor predeterminado inicialice cada elemento. Por ejemplo, sería bueno hacerlo:
BaseClass() { for (int i=0; i<virtualSize(); i++) initialize_stuff_for_index(i); }
Por otra parte, la ventaja del comportamiento de C ++ es que desalienta la creación de constructores como los anteriores.
No creo que el problema de llamar a métodos que suponen que el constructor se haya terminado sea una buena excusa para C ++. Si esto realmente fuera un problema, el constructor no podría llamar a ningún método, ya que el mismo problema puede aplicarse a los métodos para la clase base.
Otro punto en contra de C ++ es que el comportamiento es mucho menos eficiente. Aunque el constructor sabe directamente lo que llama, el puntero vtab tiene que cambiarse para cada clase desde la base hasta la final, porque el constructor puede llamar a otros métodos que invocarán funciones virtuales. Desde mi experiencia, esto consume mucho más tiempo de lo que se ahorra haciendo que las llamadas a funciones virtuales en el constructor sean más eficientes.
Mucho más molesto es que esto también sea cierto para los destructores. Si escribe una función de limpieza virtual () y el destructor de clase base limpia (), ciertamente no hace lo que espera.
Esto y el hecho de que C ++ llame a los destructores en objetos estáticos en la salida realmente me cabrearon durante mucho tiempo.