¿Cuáles son los beneficios de pasar por puntero sobre pasar por referencia en C ++?
Últimamente, he visto una serie de ejemplos que eligieron pasar argumentos de función por punteros en lugar de pasar por referencia. ¿Hay beneficios al hacer esto?
Ejemplo:
func(SPRITE *x);
con una llamada de
func(&mySprite);
vs.
func(SPRITE &x);
con una llamada de
func(mySprite);
c++
pointers
parameter-passing
pass-by-reference
Matt Pascoe
fuente
fuente
new
de crear un puntero y los problemas de propiedad resultantes.Respuestas:
Un puntero puede recibir un parámetro NULL, un parámetro de referencia no. Si alguna vez existe la posibilidad de que desee pasar "sin objeto", utilice un puntero en lugar de una referencia.
Además, pasar por puntero le permite ver explícitamente en el sitio de la llamada si el objeto se pasa por valor o por referencia:
fuente
func2 passes by reference
. Si bien agradezco que quisieras decir que pasa "por referencia" desde una perspectiva de alto nivel, implementado pasando un puntero en una perspectiva de nivel de código, esto fue muy confuso (ver stackoverflow.com/questions/13382356/… ).func(int& a)
no es válido C en ninguna versión del estándar. Probablemente esté compilando sus archivos como C ++ por accidente.Pasando por puntero
nothing
. Esto se puede usar para proporcionar argumentos opcionales.Pase por referencia
string s = &str1 + &str2;
usar punteros.void f(const T& t); ... f(T(a, b, c));
punteros no pueden usarse así, ya que no puede tomar la dirección de un temporal.fuente
Me gusta el razonamiento de un artículo de "cplusplus.com:"
Lo que deduzco de esto es que la principal diferencia entre elegir usar un puntero o un parámetro de referencia es si NULL es un valor aceptable. Eso es.
Si el valor es input, output, modificable, etc., debe estar en la documentación / comentarios sobre la función, después de todo.
fuente
"Suficiente cuerda para pegarse un tiro en el pie" de Allen Holub enumera las siguientes 2 reglas:
Enumera varias razones por las que se agregaron referencias a C ++:
const
las referencias le permiten tener una semántica de paso por valor mientras evita una copiaSu punto principal es que las referencias no deben usarse como parámetros de 'salida' porque en el sitio de la llamada no hay indicación de si el parámetro es una referencia o un parámetro de valor. Entonces su regla es usar solo
const
referencias como argumentos.Personalmente, creo que esta es una buena regla general, ya que deja más claro cuándo un parámetro es un parámetro de salida o no. Sin embargo, aunque personalmente estoy de acuerdo con esto en general, me dejo influenciar por las opiniones de otros en mi equipo si ellos defienden los parámetros de salida como referencias (a algunos desarrolladores les gustan inmensamente).
fuente
Aclaraciones a las publicaciones anteriores:
Las referencias NO son una garantía de obtener un puntero no nulo. (Aunque a menudo los tratamos como tales).
Si bien es un código horriblemente malo, como en el caso de llevarlo detrás del código incorrecto de la leñera , lo siguiente se compilará y ejecutará: (al menos en mi compilador).
El verdadero problema que tengo con las referencias radica en otros programadores, en adelante denominados IDIOTS , que asignan en el constructor, desasignan en el destructor y no proporcionan un constructor de copia u operador = ().
De repente hay un mundo de diferencia entre foo (BAR bar) y foo (BAR & bar) . (Se invoca la operación automática de copia a nivel de bit. La desasignación en el destructor se invoca dos veces).
Afortunadamente, los compiladores modernos recogerán esta doble desasignación del mismo puntero. Hace 15 años, no lo hicieron. (En gcc / g ++, use setenv MALLOC_CHECK_ 0 para volver a visitar las formas anteriores). Como resultado de DEC UNIX, la misma memoria se asigna a dos objetos diferentes. Mucha diversión de depuración allí ...
Más prácticamente:
fuente
*i
, su programa tiene un comportamiento indefinido. Por ejemplo, el compilador puede ver este código y asumir "OK, este código tiene un comportamiento indefinido en todas las rutas de código, por lo que toda esta función debe ser inalcanzable". Luego asumirá que no se toman todas las ramas que conducen a esta función. Esta es una optimización realizada regularmente.La mayoría de las respuestas aquí no abordan la ambigüedad inherente de tener un puntero en bruto en una firma de función, en términos de expresar intención. Los problemas son los siguientes:
La persona que llama no sabe si el puntero apunta a un solo objeto o al inicio de una "matriz" de objetos.
La persona que llama no sabe si el puntero "posee" la memoria a la que apunta. IE, si la función debe liberar memoria o no. (
foo(new int)
- ¿Es esto una pérdida de memoria?).La persona que llama no sabe si se
nullptr
puede pasar de forma segura a la función.Todos estos problemas se resuelven mediante referencias:
Las referencias siempre se refieren a un solo objeto.
Las referencias nunca poseen la memoria a la que se refieren, son simplemente una vista a la memoria.
Las referencias no pueden ser nulas.
Esto hace que las referencias sean un candidato mucho mejor para uso general. Sin embargo, las referencias no son perfectas, hay un par de problemas importantes a considerar.
&
operador para mostrar que de hecho estamos pasando un puntero. Por ejemplo,int a = 5; foo(a);
aquí no está nada claro que se esté pasando a por referencia y se pueda modificar.std::optional<T&>
no es válido (por buenas razones), los punteros nos dan esa nulabilidad que desea.Entonces, parece que cuando queremos una referencia anulable con indirección explícita, ¿debemos alcanzar un
T*
derecho? ¡Incorrecto!Abstracciones
En nuestra desesperación por la nulabilidad, podemos alcanzar
T*
y simplemente ignorar todas las deficiencias y la ambigüedad semántica enumeradas anteriormente. En cambio, deberíamos buscar lo que C ++ hace mejor: una abstracción. Si simplemente escribimos una clase que envuelve un puntero, obtenemos la expresividad, así como la nulabilidad y la indirección explícita.Esta es la interfaz más simple que se me ocurrió, pero hace el trabajo de manera efectiva. Permite inicializar la referencia, verificar si existe un valor y acceder al valor. Podemos usarlo así:
¡Hemos logrado nuestros objetivos! Veamos ahora los beneficios, en comparación con el puntero sin formato.
nullptr
se puede transmitir, ya que el autor de la función solicita explícitamente unoptional_ref
Podríamos hacer que la interfaz sea más compleja desde aquí, como agregar operadores de igualdad, una monádica
get_or
y unamap
interfaz, un método que obtiene el valor o arroja una excepción,constexpr
soporte. Eso lo puedes hacer tú.En conclusión, en lugar de usar punteros sin procesar, razone sobre lo que esos punteros realmente significan en su código, y aproveche una abstracción de biblioteca estándar o escriba la suya propia. Esto mejorará su código significativamente.
fuente
Realmente no. Internamente, el paso por referencia se realiza esencialmente pasando la dirección del objeto referenciado. Por lo tanto, realmente no hay ganancias de eficiencia al pasar un puntero.
Sin embargo, pasar por referencia tiene un beneficio. Se garantiza que tendrá una instancia de cualquier objeto / tipo que se esté pasando. Si pasa un puntero, corre el riesgo de recibir un puntero NULL. Al usar el paso por referencia, está empujando una comprobación NULL implícita de un nivel a la persona que llama de su función.
fuente