2008-09-22 18 views
225

¿Cuál sería una mejor práctica cuando se da una función de la variable original para trabajar con:puntero vs. Referencia

unsigned long x = 4; 

void func1(unsigned long& val) { 
    val = 5;    
} 
func1(x); 

o:

void func2(unsigned long* val) { 
    *val = 5; 
} 
func2(&x); 

OIA: ¿Hay alguna razón para elegir uno sobre otro ?

+1

Las referencias son por supuesto valiosas, pero vengo de C, donde los punteros están en todas partes. Uno tiene que ser competente con punteros primero para comprender el valor de las referencias. –

+0

¿Cómo encaja esto con un objetivo como la transparencia referencial de la programación funcional? ¿Qué sucede si siempre quiere que las funciones devuelvan nuevos objetos y nunca mute el estado internamente, especialmente no de las variables pasadas a la función? ¿Hay alguna manera de que este concepto se use con punteros y referencias en un lenguaje como C++? (Nota, supongo que alguien ya tiene el objetivo de la transparencia referencial.No me interesa hablar sobre si es un buen objetivo o no.) – ely

+0

Preferir referencias. Punteros de usuario cuando no tiene otra opción. – Ferruccio

Respuesta

254

Mi regla de oro es:

utilizar punteros si usted quiere hacer la aritmética de punteros con ellos (por ejemplo, incrementando la dirección del puntero al paso a través de una matriz) o si alguna vez tiene que pasar un valor NULL-puntero.

Use referencias de lo contrario.

+9

Excelente punto con respecto a un puntero que es NULL. Si tiene un parámetro de puntero, debe verificar explícitamente que no es NULO o buscar todos los usos de la función para asegurarse de que nunca sea NULO. Este esfuerzo no es necesario para las referencias. –

+25

Explica qué quieres decir con aritmética. Es posible que un usuario nuevo no comprenda que desea ajustar a qué apunta el puntero. –

+6

Martin, Por aritmética me refiero a que pasas un puntero a una estructura pero sabes que no es una estructura simple sino una matriz de ella. En este caso, puede indexarlo usando [] o hacer aritmética usando ++/- en el puntero. Esa es la diferencia en pocas palabras. –

63

realmente creo que se beneficiarán de establecer la siguiente función de llamar a las directrices de codificación:

  1. Como en todos los demás lugares, siempre esté const -correct.

    • Nota: Esto significa, entre otras cosas, que sólo fuera de los valores (ver punto 3) y los valores pasados ​​por valor (véase el punto 4) puede carecer de la especificador const.
  2. pasar únicamente por un valor de puntero si el valor 0/NULL es una entrada válida en el contexto actual.

    • Razón 1: Como una persona que llama , se ve que todo lo que pasa en debe ser en un estado utilizable.

    • Razón 2: Como llama, sabes que todo lo que viene en es en un estado utilizable. Por lo tanto, no es necesario realizar ningún control NULL o tratamiento de errores para ese valor.

    • Justificación 3: Rationales 1 y 2 serán compilador forzado. Siempre capture los errores en tiempo de compilación si puede.

  3. Si un argumento de función es un valor de salida, páselo por referencia.

    • Justificación: No queremos romper el punto 2 ...
  4. elija "paso por valor" sobre "el paso por referencia const" sólo si el valor es un POD (Plain old Datastructure) o lo suficientemente pequeño (en cuanto a la memoria) o en otras formas lo suficientemente barato (a tiempo) para copiar.

    • Justificación: Evite copias innecesarias.
    • Nota: lo suficientemente pequeño y bastante barato no son medibles absolutos.
+0

Carece de la guía cuando: ... "cuando se usa const &" ... La guía 2 debe escribirse "para valores [in], solo pase por puntero si NULL es válido. De lo contrario, use referencia constante (o para objetos "pequeños", copia), o referencia si es un valor [fuera]. Estoy monitoreando esta publicación para agregar potencialmente un +1. – paercebal

+0

El ítem 1 cubre el caso que describes. –

+0

Es un poco difícil de aprobar. -parámetro por referencia si no es predeterminado-construible. Eso es bastante común en mi código: la razón completa para tener una función de crear ese objeto externo es porque no es trivial. – MSalters

1

Pasar por referencia const a menos que exista una razón que desea cambiar/mantener el contenido está de paso en.

Este será el método más eficaz en la mayoría de los casos.

Asegúrese de usar const en cada parámetro que no desee cambiar, ya que esto no solo lo protege de hacer algo estúpido en la función, sino que le da una buena indicación a otros usuarios de lo que hace la función a los valores pasados . Esto incluye hacer un puntero const cuando solo quiere cambiar lo que apunta a ...

22

Esto finalmente termina siendo subjetivo. La discusión hasta ahora es útil, pero no creo que haya una respuesta correcta o decisiva a esto. Mucho dependerá de las pautas de estilo y sus necesidades en ese momento.

Si bien hay algunas capacidades diferentes (si algo puede o no ser NULO) con un puntero, la mayor diferencia práctica para un parámetro de salida es puramente sintaxis. La Guía de estilo C++ de Google (https://google.github.io/styleguide/cppguide.html#Reference_Arguments), por ejemplo, solo exige punteros para los parámetros de salida, y solo permite las referencias que son const. El razonamiento es uno de legibilidad: algo con sintaxis de valor no debe tener un significado semántico de puntero. No estoy sugiriendo que esto sea necesariamente correcto o incorrecto, pero creo que el punto aquí es que es una cuestión de estilo, no de corrección.

+0

¿Qué significa que las referencias tienen sintaxis de valor pero significado semántico de puntero? –

+0

Parece que está pasando una copia ya que la parte "pasar por referencia" solo es aparente desde la definición de la función (sintaxis de valor), pero no está copiando el valor que pasa, esencialmente pasa un puntero debajo del capó, que permite que la función modifique su valor. – phant0m

2

Una referencia es un puntero implícito. Básicamente puede cambiar el valor al que apunta la referencia pero no puede cambiar la referencia para señalar a otra cosa. Así que mis 2 centavos es que si solo quieres cambiar el valor de un parámetro, pásalo como referencia, pero si necesitas cambiar el parámetro para apuntar a un objeto diferente, pásalo usando un puntero.

6

Debe pasar un puntero si va a modificar el valor de la variable. Aunque técnicamente pasar una referencia o un puntero son los mismos, pasar un puntero en su caso de uso es más legible ya que "anuncia" el hecho de que la función va a cambiar el valor.

+2

Si sigue las pautas de Johann Gerell, una referencia no constante también anuncia una variable variable, por lo que el puntero no tiene esa ventaja aquí. –

+3

@AlexanderKondratskiy: te estás perdiendo el punto ... no puedes ver al instante * en el sitio de llamadas * si la función llamada acepta un parámetro como una referencia 'const' o' 'non-'const', pero puedes ver si el parámetro pasó ala '& x' contra 'x', y usa esa convensión para codificar si el parámetro puede ser modificado. (Dicho esto, hay momentos en los que querrás pasar un puntero 'const', por lo que la convección es solo una pista. Sospechoso sospecha de que algo podría modificarse cuando no sea así, es menos peligroso que pensar que no será cuando será ....) –

4

Si tiene un parámetro donde puede necesitar indicar la ausencia de un valor, es una práctica común hacer que el parámetro sea un valor de puntero y pase en NULL.

Una mejor solución en la mayoría de los casos (desde una perspectiva de seguridad) es usar boost::optional. Esto le permite ingresar valores opcionales por referencia y también como valor de retorno.

// Sample method using optional as input parameter 
void PrintOptional(const boost::optional<std::string>& optional_str) 
{ 
    if (optional_str) 
    { 
     cout << *optional_str << std::endl; 
    } 
    else 
    { 
     cout << "(no string)" << std::endl; 
    } 
} 

// Sample method using optional as return value 
boost::optional<int> ReturnOptional(bool return_nothing) 
{ 
    if (return_nothing) 
    { 
     return boost::optional<int>(); 
    } 

    return boost::optional<int>(42); 
} 
2

Considera la palabra clave C#. El compilador requiere que el llamante de un método aplique la palabra clave out a cualquier argumento externo, aunque ya sepa si lo está. Esto tiene la intención de mejorar la legibilidad. Aunque con los IDEs modernos me inclino a pensar que este es un trabajo para resaltar la sintaxis (o semántica).

+0

typo: semántico, no sísmico; +1 Acepto la posibilidad de resaltar en lugar de escribir (C#), o & (en el caso de C, sin referencias) – peenut

1

Punteros:

  • puede asignarse nullptr (o NULL).
  • En el sitio de llamadas debe usar & si su tipo no es un puntero en sí, haciendo explícito que está modificando su objeto.
  • Los apuntadores pueden rebotar.

Referencias:

  • no puede ser nulo.
  • Una vez encuadernado, no se puede cambiar.
  • Las personas que llaman no necesitan explícitamente usar &. Esto se considera a veces malo porque debe ir a la implementación de la función para ver si su parámetro está modificado.
+0

Un pequeño punto para aquellos que no saben: nullptr o NULL es simplemente un 0. http: // stackoverflow.com/questions/462165/error-null-was-not-declared-in-this-scope –

+1

nullptr no es lo mismo que 0. Pruebe int a = nullptr; http://stackoverflow.com/questions/1282295/what-excaly-is-nullptr –

4

Punteros vs. Refereces

Referencias son menos potentes que los punteros:

1) Una vez que se crea una referencia, no puede ser más tarde hizo para hacer referencia a otro objeto; no puede ser resecado Esto se hace a menudo con punteros.

2) Las referencias no pueden ser NULAS. Los punteros a menudo se hacen NULL para indicar que no están apuntando a ninguna cosa válida.

3) Una referencia debe inicializarse cuando se declara. No hay tal restricción con punteros

Debido a las limitaciones anteriores, referencias en C++ no se pueden utilizar para implementar estructuras de datos como lista enlazada, Árbol, etc. En Java, las referencias no tienen por encima de las restricciones, y se puede usar para implementar todas las estructuras de datos. Las referencias son más poderosas en Java, es la razón principal por la que Java no necesita punteros.

Las referencias son más seguros y más fáciles de usar:

1) más seguro: Desde las referencias deben ser inicializadas, referencias salvajes como punteros salvajes es improbable que existan. Todavía es posible tener referencias que no se refieran a una ubicación válida

2) Más fácil de usar: las referencias no necesitan que el operador de desreferenciación tenga acceso al valor. Se pueden usar como variables normales. El operador '&' solo es necesario en el momento de la declaración. Además, se puede acceder a los miembros de una referencia de objeto con el operador de punto ('.'), A diferencia de los punteros donde se necesita el operador de flecha (->) para acceder a los miembros.

Junto con las razones anteriores, hay pocos lugares como el argumento del constructor de copia donde no se puede usar el puntero. La referencia debe usarse para pasar el argumento en el constructor de copia. Del mismo modo referencias se deben utilizar para sobrecargar algunos operadores como ++.

Cuestiones relacionadas