C++: ¿Cuáles son los escenarios en los que el uso de punteros es una "buena idea" (TM)? [duplicar]
Posible duplicado:
¿usos comunes de los punteros?
Todavía estoy aprendiendo los conceptos básicos de C++ pero ya sé lo suficiente como para hacer pequeños programas útiles.
Entiendo el concepto de punteros y los ejemplos que veo en los tutoriales tienen sentido para mí. Sin embargo, en el nivel práctico, y siendo un (ex) desarrollador de PHP, todavía no estoy seguro de usarlos en mis programas.
De hecho, hasta ahora no he sentido la necesidad de utilizar ningún puntero. Tengo mis clases y funciones y parece que lo estoy haciendo perfectamente bien sin usar ningún puntero (y mucho menos punteros a punteros). Y no puedo evitar sentirme un poco orgulloso de mis pequeños programas.
Aun así, soy consciente de que me falta una de las características más importantes de C++ , una de doble filo: los punteros y la gestión de la memoria pueden crear estragos, fallos aparentemente aleatorios, errores difíciles de encontrar y agujeros de seguridad... pero al mismo tiempo, utilizados correctamente, deben permitir una programación inteligente y eficiente .
Entonces: dígame qué me falta al no usar punteros.
¿ Cuáles son buenos escenarios en los que es imprescindible utilizar punteros?
¿Qué te permiten hacer que no podrías hacer de otra manera?
¿De qué manera pueden hacer que sus programas sean más eficientes?
¿Y qué pasa con los consejos sobre consejos?
[Editar: todas las respuestas son útiles. Un problema en SO es que no podemos "aceptar" más de una respuesta. Muchas veces desearía poder hacerlo. En realidad, son todas las respuestas combinadas las que ayudan a comprender mejor el panorama completo. Gracias.]
Utilizo punteros cuando quiero darle acceso a una clase a un objeto, sin darle propiedad sobre ese objeto. Incluso entonces, puedo usar una referencia, a menos que necesite poder cambiar a qué objeto estoy accediendo y/o necesite la opción de ningún objeto, en cuyo caso el puntero sería NULL.
Esta pregunta se ha hecho antes en SO . Mi respuesta desde allí:
Utilizo punteros aproximadamente una vez cada seis líneas en el código C++ que escribo. En lo que se me ocurre, estos son los usos más comunes:
- Cuando necesito crear dinámicamente un objeto cuya vida útil excede el alcance en el que fue creado.
- Cuando necesito asignar un objeto cuyo tamaño se desconoce en el momento de la compilación.
- Cuando necesito transferir la propiedad de un objeto de una cosa a otra sin realmente copiarlo (como en una lista vinculada/montón/lo que sea de estructuras realmente grandes y costosas)
- Cuando necesito referirme al mismo objeto desde dos lugares diferentes.
- Cuando necesito dividir una matriz sin copiarla.
- Cuando necesito usar elementos intrínsecos del compilador para generar instrucciones específicas de la CPU o solucionar situaciones en las que el compilador emite código subóptimo o ingenuo.
- Cuando necesito escribir directamente en una región específica de la memoria (porque tiene IO asignada en memoria).
Los punteros se utilizan comúnmente en C++. Sentirse cómodo con ellos le ayudará a comprender una gama más amplia de códigos. Dicho esto, si puedes evitarlos, eso es genial; sin embargo, con el tiempo, a medida que tus programas se vuelvan más complejos, es probable que los necesites, aunque solo sea para interactuar con otras bibliotecas.
Principalmente los punteros se utilizan para hacer referencia a la memoria asignada dinámicamente (devuelta por
new
).Permiten que las funciones tomen argumentos que no se pueden copiar en la pila porque son demasiado grandes o no se pueden copiar, como un objeto devuelto por una llamada al sistema. (Creo que también la alineación de la pila puede ser un problema, pero es demasiado confusa para estar seguro).
En la programación integrada se utilizan para referirse a cosas como registros de hardware, que requieren que el código se escriba en una dirección muy específica en la memoria.
Los punteros también se utilizan para acceder a objetos a través de sus interfaces de clase base.
class B : public A {}
Eso es si tengo una clase B que se deriva de la clase A. Es decir, se puede acceder a una instancia del objeto B como si fuera de clase A proporcionando su dirección a un puntero a la clase A, es decir:A *a = &b_obj;
Es un modismo de C utilizar punteros como iteradores en matrices. Esto todavía puede ser común en el código C++ más antiguo, pero probablemente se considere un primo pobre de los objetos iteradores STL.
Si necesita interactuar con código C, siempre necesitará manejar punteros que se utilizan para hacer referencia a objetos asignados dinámicamente, ya que no hay referencias . Las cadenas C son solo punteros a una matriz de caracteres terminados en el carácter nulo '\0'.
Una vez que se sienta cómodo con los consejos, los consejos sobre los consejos no le parecerán tan horribles. El ejemplo más obvio es la lista de argumentos para main()
. Por lo general, esto se declara como char *argv[]
, pero lo he visto declarado (creo que legalmente) como char **argv
.
La declaración es de estilo C, pero dice que tengo una serie de punteros a punteros a caracteres. Que se interpreta como una matriz de tamaño arbitrario (el tamaño lo lleva argc) de cadenas de estilo C (matrices de caracteres terminadas en el carácter nulo '\0').
Si no ha sentido la necesidad de recibir sugerencias, no perdería mucho tiempo preocupándome por ellas hasta que surja la necesidad.
Dicho esto, una de las principales formas en que los punteros pueden contribuir a una programación más eficiente es evitando copias de datos reales. Por ejemplo, supongamos que está escribiendo una pila de red. Recibe un paquete Ethernet para ser procesado. Usted pasa sucesivamente esos datos por la pila desde el controlador Ethernet "sin procesar" al controlador IP, al controlador TCP y, por ejemplo, al controlador HTTP a algo que procesa el HTML que contiene.
Si está haciendo una nueva copia del contenido para cada uno de ellos, terminará haciendo al menos cuatro copias de los datos antes de poder renderizarlos.
El uso de punteros puede evitar mucho de eso: en lugar de copiar los datos en sí, simplemente pasa un puntero a los datos. Cada capa sucesiva de la pila de red mira su propio encabezado y pasa un puntero a lo que considera la "carga útil" hasta la siguiente capa superior de la pila. La siguiente capa mira su propio encabezado, modifica el puntero para mostrar lo que considera la carga útil y lo pasa a la pila. En lugar de cuatro copias de los datos, las cuatro capas funcionan con una copia de los datos reales.