Puntero vs. referencia

256

Lo que sería una mejor práctica al dar a una función la variable original con la que trabajar:

unsigned long x = 4;

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

o:

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

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

Jack Reza
fuente
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 los punteros primero para comprender el valor de las referencias.
Jay D
¿Cómo encaja esto con un objetivo como la transparencia referencial de la programación funcional? ¿Qué sucede si siempre desea que las funciones devuelvan nuevos objetos y nunca muten internamente el estado, especialmente no de las variables pasadas a la función? ¿Hay alguna manera de que este concepto todavía se use con punteros y referencias en un lenguaje como C ++? (Nota, estoy suponiendo que alguien ya tiene el objetivo de transparencia referencial no estoy interesado en hablar de si es o no es un objetivo bueno tener..)
Ely
Prefiero referencias. Punteros de usuario cuando no tienes otra opción.
Ferruccio

Respuestas:

285

Mi regla de oro es:

Utilice punteros si desea hacer aritmética de punteros con ellos (por ejemplo, incrementar la dirección del puntero para recorrer una matriz) o si alguna vez tiene que pasar un puntero NULL.

Use referencias de otra manera.

Nils Pipenbrinck
fuente
10
Excelente punto con respecto a un puntero siendo NULL. Si tiene un parámetro de puntero, debe verificar explícitamente que no es NULL o buscar todos los usos de la función para asegurarse de que nunca sea NULL. Este esfuerzo no es necesario para las referencias.
Richard Corden
26
Explica qué quieres decir con aritmética. Es posible que un nuevo usuario no entienda que desea ajustar lo que señala el puntero.
Martin York
77
Martin, por aritmética quiero decir 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.
Nils Pipenbrinck el
2
Martin, solo puedes hacer esto con punteros directamente. No con referencias. Claro que puede llevar un puntero a una referencia y hacer lo mismo en la práctica, pero si lo hace, termina con un código muy sucio ...
Nils Pipenbrinck
1
¿Qué pasa con el polimorfismo (por ejemplo Base* b = new Derived())? Este parece un caso que no se puede manejar sin punteros.
Chris Redford
72

Realmente creo que se beneficiará al establecer las siguientes pautas de codificación de llamadas a funciones:

  1. Como en todos los otros lugares, siempre sea constcorrecto.

    • Nota: Esto significa, entre otras cosas, que solo los valores externos (ver ítem 3) y los valores pasados ​​por valor (ver ítem 4) pueden carecer del constespecificador.
  2. Solo pase un valor por puntero si el valor 0 / NULL es una entrada válida en el contexto actual.

    • Justificación 1: como llamante , verá que todo lo que pase debe estar en un estado utilizable.

    • Justificación 2: Como se llama , usted sabe que todo lo que entra está en un estado utilizable. Por lo tanto, no es necesario realizar ninguna comprobación de NULL o manejo de errores para ese valor.

    • Justificación 3: Las justificaciones 1 y 2 se aplicarán al compilador . Siempre puede detectar errores en tiempo de compilación si puede.

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

    • Justificación: No queremos romper el ítem 2 ...
  4. Elija "pasar por valor" sobre "pasar por referencia constante" solo si el valor es un POD ( Estructura de datos antigua simple ) o lo suficientemente pequeño (en cuanto a memoria) o en otras formas lo suficientemente barato (en cuanto a tiempo) para copiar.

    • Justificación: evite copias innecesarias.
    • Nota: lo suficientemente pequeño y barato no son mensurables absolutos.
Johann Gerell
fuente
Carece de la directriz cuando: ... "cuándo usar const &" ... La directriz 2 debe escribirse "para valores [en], solo pase por puntero si NULL es válido. De lo contrario, use la referencia const (o para" "objetos pequeños, copia) o referencia si es un valor [out]. Estoy monitoreando esta publicación para agregar potencialmente un +1.
paercebal
El ítem 1 cubre el caso que usted describe.
Johann Gerell
Es un poco difícil pasar un parámetro de salida por referencia si no es construible por defecto. Eso es bastante común en mi código: toda la razón para que una función cree ese objeto externo es porque no es trivial.
MSalters
@MSalters: Si va a asignar la memoria dentro de la función (que creo que es lo que quiere decir), ¿por qué no simplemente devolver un puntero a la memoria asignada?
Kleist
@Kleist: En nombre de @MSalters, hay muchas razones posibles. Una es que es posible que ya haya asignado memoria para llenar, como un tamaño predeterminado std::vector<>.
Johann Gerell
24

Esto finalmente termina siendo subjetivo. La discusión hasta el momento 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 ser NULO o no) 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, exige solo punteros para los parámetros de salida y solo permite referencias que sean constantes. El razonamiento es 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.

Aaron N. Tubbs
fuente
¿Qué significa que las referencias tienen sintaxis de valor pero significado semántico de puntero?
Eric Andrew Lewis
Parece que está pasando una copia, ya que la parte "pasar por referencia" solo es aparente a partir de la definición de función (sintaxis de valor), pero no está copiando el valor que pasa, esencialmente pasa un puntero debajo del capó, lo que permite La función para modificar su valor.
phant0m
No hay que olvidar que la guía de estilo de Google C ++ es muy detestada.
Deduplicador
7

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

Max Caceres
fuente
2
Si sigue las pautas de Johann Gerell, una referencia no constante también anuncia una variable cambiable, por lo que el puntero no tiene esa ventaja aquí.
Alexander Kondratskiy
44
@AlexanderKondratskiy: te estás perdiendo el punto ... no puedes ver instantáneamente en el sitio de la llamada si la función llamada acepta un parámetro como una referencia consto no const, pero puedes ver si el parámetro pasó ala &xvs. xy usar esa convección para codificar si el parámetro puede modificarse. (Dicho esto, hay momentos en los que querrás pasar un constpuntero, por lo que la convección es solo una pista. Argumentable sospechar que algo podría modificarse cuando no sea así es menos peligroso que pensar que no será cuando será ....)
Tony Delroy
5

Si tiene un parámetro en el que 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 pasar NULL.

Una mejor solución en la mayoría de los casos (desde una perspectiva de seguridad) es usar boost :: opcional . Esto le permite pasar 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);
}
Kiley Hykawy
fuente
4

Punteros

  • Un puntero es una variable que contiene una dirección de memoria.
  • Una declaración de puntero consta de un tipo base, un * y el nombre de la variable.
  • Un puntero puede apuntar a cualquier número de variables en la vida
  • Un puntero que actualmente no apunta a una ubicación de memoria válida recibe el valor nulo (que es cero)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • El & es un operador unario que devuelve la dirección de memoria de su operando.

  • El operador de desreferenciación (*) se utiliza para acceder al valor almacenado en la variable a la que apunta el puntero.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Referencia

  • Una referencia (&) es como un alias de una variable existente.

  • Una referencia (&) es como un puntero constante que se desreferencia automáticamente.

  • Generalmente se usa para listas de argumentos de funciones y valores de retorno de funciones.

  • Se debe inicializar una referencia cuando se crea.

  • Una vez que se inicializa una referencia a un objeto, no se puede cambiar para referirse a otro objeto.

  • No puede tener referencias NULL.

  • Una referencia constante puede referirse a una constante int. Se realiza con una variable temporal con valor de la constante

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Saurabh Raoot
fuente
3

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 que apunte a otra cosa. Entonces, mis 2 centavos es que si solo desea cambiar el valor de un parámetro, páselo como referencia, pero si necesita cambiar el parámetro para que apunte a un objeto diferente, páselo con un puntero.


fuente
3

Considere la palabra clave de C #. El compilador requiere que el llamador de un método aplique la palabra clave out a cualquier argumento out, aunque ya sabe si lo está. Esto está destinado a mejorar la legibilidad. Aunque con los IDE modernos, me inclino a pensar que este es un trabajo para resaltar la sintaxis (o semántica).

Daniel Earwicker
fuente
error tipográfico: semántico, no simbólico; 1 Estoy de acuerdo con la posibilidad de resaltar en lugar de escribir (C #) o Y (en el caso de C, no hay referencias)
peenut
2

Pase por referencia constante a menos que haya una razón por la que desea cambiar / conservar el contenido que está pasando.

Este será el método más eficiente 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 la función hace a los valores pasados. Esto incluye hacer un puntero constante cuando solo quieres cambiar lo que se señala ...

No jarvis
fuente
2

Punteros:

  • Se puede asignar nullptr(o NULL).
  • En el sitio de la llamada, debe usar &si su tipo no es un puntero en sí mismo, lo que explícitamente está modificando su objeto.
  • Los punteros pueden ser rebotados.

Referencias

  • No puede ser nulo.
  • Una vez atado, no puede cambiar.
  • Las personas que llaman no necesitan usar explícitamente &. Esto se considera a veces malo porque debe ir a la implementación de la función para ver si se modifica su parámetro.
Germán Diago
fuente
Un pequeño punto para aquellos que no saben: nullptr o NULL es simplemente un 0. stackoverflow.com/questions/462165/…
Serguei Fedorov
2
nullptr no es lo mismo que 0. Pruebe int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg
0

Una referencia es similar a un puntero, excepto que no necesita usar un prefijo ∗ para acceder al valor al que hace referencia la referencia. Además, no se puede hacer una referencia para referirse a un objeto diferente después de su inicialización.

Las referencias son particularmente útiles para especificar argumentos de función.

Para obtener más información, consulte "Un recorrido por C ++" de "Bjarne Stroustrup" (2014) Páginas 11-12

amirfg
fuente