creo que hay varias cosas aquí:
- diferencia entre la variable automática y dinámicamente asignada variables
- curso de la vida de los objetos
- RAII
- C# paralelo
automática vs Dinámico
Una variable automática es una variable cuyo sistema administrará la vida útil. Vamos a zanja variables globales en este momento, es complicado, y concentrarse en el caso habitual:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
Aquí sc
es una variable automática. Se garantiza que se habrá inicializado por completo (es decir, se garantiza que el constructor se habrá ejecutado) después de que la ejecución de la línea (3) se haya completado correctamente. Su destructor se invocará automáticamente en la línea (6).
Generalmente hablamos del alcance de una variable: desde el punto de declaración hasta el corchete de cierre correspondiente; y el lenguaje garantiza la destrucción cuando se salga del alcance, ya sea con un return
o una excepción.
Por supuesto, no hay garantía en el caso de que invoque el temido "Comportamiento indefinido", que generalmente resulta en un bloqueo.
Por otro lado, C++ también tiene variables dinámicas, es decir, variables que asigna usando new
.
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass* sc = 0; // 3
sc = new SomeClass(); // 4
sc->foo(); // 5
return 0; // 6
} // 7 (!! leak)
Aquí sc
sigue siendo una variable automática, sin embargo difieren en su tipo: ahora es un puntero a una variable de tipo SomeClass
.
En la línea (3) sc
se le asigna un valor de puntero null (nullptr
en C++ 0x) porque no apunta a cualquier instancia de SomeClass
. Tenga en cuenta que el idioma no garantiza ninguna inicialización por sí mismo, por lo que debe asignar algo explícitamente; de lo contrario, tendrá un valor de basura.
En la línea (4) creamos una variable dinámica (utilizando el operador new
) y asignamos su dirección a sc
. Tenga en cuenta que la variable dinámica en sí no tiene nombre, el sistema solo nos proporciona un puntero (dirección).
En la línea (7) el sistema destruye automáticamente sc
, sin embargo, no destruye la variable dinámica que señalaba, y por lo tanto ahora tenemos una variable dinámica cuya dirección no está almacenada en ninguna parte. A menos que usemos un recolector de basura (que no es el caso en C++ estándar), hemos filtrado la memoria, ya que la memoria de la variable no se recuperará antes de que el proceso finalice ... e incluso entonces el destructor no se ejecutará (muy mal si tuviera efectos secundarios).
por vida de objetos
Herb Sutter tiene unos artículos muy interesantes sobre este tema. Aquí está the first.
A modo de resumen:
- Un objeto vive tan pronto como su constructor ejecuta hasta el final. Significa que si el constructor tira, el objeto nunca vivió (considéralo un accidente de embarazo).
- Un objeto está muerto tan pronto como se invoca su destructor, si el destructor arroja (esto es MAL) no se puede intentar de nuevo porque no puede invocar ningún método en un objeto muerto, es un comportamiento indefinido.
Si volvemos al primer ejemplo:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
sc
está vivo de la línea (4) a la línea (5), ambos inclusive. En la línea (3) está siendo construido (que puede fallar por cualquier cantidad de razones) y en la línea (6) está siendo destruido.
RAII
RAII significa Recursos adquisición es de inicialización. Es una expresión idiomática administrar los recursos, y especialmente asegurar que los recursos finalmente se liberarán una vez que se hayan adquirido.
En C++, ya que no tenemos recolección de basura, esta expresión se aplica principalmente a la gestión de memoria, pero también es útil para cualquier otro tipo de recursos: bloqueos en entornos multiproceso, bloqueos de archivos, enchufes/conexiones en red, etc. ...
Cuando se utiliza para la gestión de memoria, se utiliza para acoplar la vida útil de la variable dinámica a la vida útil de un conjunto determinado de variables automáticas, garantizando que la variable dinámica no las perdure (y se pierda).
En su forma más simple, es acoplada a una única variable automática:
int main(int argc, char* argv[])
{
std::unique_ptr<SomeClass> sc = new SomeClass();
sc->foo();
return 0;
}
Es muy similar al primer ejemplo, excepto que asignar dinámicamente una instancia de SomeClass
. La dirección de esta instancia se entrega al objeto sc
, de tipo std::unique_ptr<SomeClass>
(es una instalación de C++ 0x, use boost::scoped_ptr
si no está disponible). unique_ptr
garantiza que el objeto señalado se destruirá cuando se destruya sc
.
En una forma más complicada, podría asociarse a varias variables automáticas usando (por ejemplo) std::shared_ptr
, que como su nombre lo indica permite compartir un objeto y garantiza que el objeto se destruirá cuando se destruya el último participante. Tenga en cuenta que esto no es equivalente a usar un recolector de basura y que puede haber problemas con los ciclos de referencias, no profundizaré aquí, así que recuerde que std::shared_ptr
no es una panacea.
Debido a que es muy complicado de manejar perfectamente la vida de una variable dinámica sin RAII en la cara de las excepciones y código multiproceso, la recomendación es:
- uso de variables automáticas tanto como sea posible
- para la dinámica las variables, nunca se invocan
delete
por su cuenta y siempre hace uso de las instalaciones RAII
personalmente considero cualquier ocurrencia de delete
ser fuertemente sospechoso, y yo todos los días s pida su eliminación en las revisiones del código: es un olor a código.
C# paralelo
En C# que utilizan, sobre todo las variables dinámicas *
. Esta es la razón:
- Si sólo declara una variable, sin asignación, su valor es nulo: en esencia, sólo se está manipulando punteros y que por lo tanto tiene un puntero nulo (se garantiza la inicialización, gracias a Dios)
- Utiliza
new
para crear valores, esto invoca el constructor de su objeto y le da la dirección del objeto; observe cómo la sintaxis es similar a C++ para las variables dinámicas
Sin embargo, a diferencia de C++, C# es basura recolectada por lo que no tiene que preocuparse por la administración de la memoria.
La recolección de basura también significa que la vida útil de los objetos es más difícil de entender: se crean cuando se solicitan pero se destruyen a la conveniencia del sistema. Esto puede ser un problema para implementar RAII, por ejemplo, si realmente desea liberar el bloqueo rápidamente, y el lenguaje tiene una serie de recursos para ayudarlo con using
palabra clave + IDisposable
interfaz de la memoria.
*
: es fácil de verificar, si después de declarar una variable su valor es null
, entonces será una variable dinámica. Creo que para int
el valor será 0 indicando que no lo es, pero ya han pasado 3 años desde que jugueteé con C# para un proyecto de curso así que ...
"Como vengo del mundo C#" ¿Entonces? C++ no es C#, olvida que sabes C# en absoluto. Necesitas un [buen libro para principiantes] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list), y comienzas desde cero. – GManNickG
¡Gracias por la lista! Es cierto que C++ y C# son dos idiomas muy diferentes. Supongo que simplemente no quiero leer acerca de qué bucles o clases son y más acerca de cómo funcionan en C++, por lo tanto, he estado evitando los libros. Veré la lista. –
@Michael: no puedes evitar los libros. :) C++ es su propio idioma. Parece que crees que ya entiendes las clases de C++, pero no son lo mismo que en C#. Claro, es posible que ya sepas qué son las declaraciones de selección o iteración, pero eso es solo un capítulo. – GManNickG