Cuándo usar referencias vs. punteros

381

Entiendo la sintaxis y la semántica general de los punteros versus las referencias, pero ¿cómo debo decidir cuándo es más o menos apropiado usar referencias o punteros en una API?

Naturalmente, algunas situaciones necesitan una u otra ( operator++necesita un argumento de referencia), pero en general estoy descubriendo que prefiero usar punteros (y punteros constantes) ya que la sintaxis es clara de que las variables se pasan de manera destructiva.

Por ejemplo, en el siguiente código:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

Con el puntero, siempre es (más) obvio lo que está sucediendo, por lo que para las API y similares donde la claridad es una gran preocupación, ¿los punteros no son más apropiados que las referencias? ¿Eso significa que las referencias solo deben usarse cuando sea necesario (por ejemplo operator++)? ¿Hay problemas de rendimiento con uno u otro?

EDITAR (ACTUALIZADO):

Además de permitir valores NULL y tratar con matrices sin procesar, parece que la elección se reduce a preferencias personales. Acepté la respuesta a continuación que hace referencia a la Guía de estilo C ++ de Google , ya que presentan la opinión de que "las referencias pueden ser confusas, ya que tienen una sintaxis de valor pero una semántica de puntero".

Debido al trabajo adicional requerido para desinfectar los argumentos del puntero que no deberían ser NULL (por ejemplo add_one(0), llamará a la versión del puntero y se interrumpirá durante el tiempo de ejecución), tiene sentido desde una perspectiva de mantenimiento usar referencias donde un objeto DEBE estar presente, aunque es una pena perder la claridad sintáctica

Connec
fuente
44
Parece que ya ha tomado su decisión sobre cuál usar cuándo. Personalmente, prefiero pasar el objeto sobre el que estoy actuando, ya sea que lo esté modificando o no. Si una función toma un puntero, eso me dice que está actuando sobre punteros, es decir, usándolos como iteradores en una matriz.
Benjamin Lindley
1
@Schnommus: Bastante justo, principalmente uso TextMate. Aún así, creo que es preferible que el significado sea obvio de un vistazo.
Connec
44
¿Qué pasa si add_one(a);no está claro si se ava a modificar? Dice justo en el código: agregue uno .
GManNickG
32
@connec: la guía de estilo C ++ de Google no se considera una buena guía de estilo C ++. Es una guía de estilo para trabajar con la antigua base de código C ++ de Google (es decir, buena para sus cosas). Aceptar una respuesta basada en eso no ayuda a nadie. Simplemente leyendo sus comentarios y explicaciones, llegó a esta pregunta con una opinión ya establecida y solo está buscando a otras personas para confirmar su opinión. Como resultado, está basando la pregunta y la respuesta a lo que desea / espera escuchar.
Martin York
1
Esto se soluciona simplemente nombrando el método addOneTo(...). Si eso no es lo que quieres hacer, solo mira la declaración.
stefan

Respuestas:

297

Utilice la referencia siempre que pueda, los punteros donde sea necesario.

Evita los punteros hasta que no puedas.

La razón es que los punteros hacen que las cosas sean más difíciles de seguir / leer, manipulaciones menos seguras y mucho más peligrosas que cualquier otra construcción.

Entonces, la regla general es usar punteros solo si no hay otra opción.

Por ejemplo, devolver un puntero a un objeto es una opción válida cuando la función puede devolver nullptr en algunos casos y se supone que lo hará. Dicho esto, una mejor opción sería usar algo similar a boost::optional.

Otro ejemplo es usar punteros a la memoria sin procesar para manipulaciones de memoria específicas. Eso debería estar oculto y localizado en partes muy estrechas del código, para ayudar a limitar las partes peligrosas de toda la base del código.

En su ejemplo, no tiene sentido usar un puntero como argumento porque:

  1. si proporcionas nullptrcomo argumento, vas a una tierra de comportamiento indefinido;
  2. la versión del atributo de referencia no permite (sin trucos fáciles de detectar) el problema con 1.
  3. La versión del atributo de referencia es más fácil de entender para el usuario: debe proporcionar un objeto válido, no algo que pueda ser nulo.

Si el comportamiento de la función tuviera que funcionar con o sin un objeto dado, entonces usar un puntero como atributo sugiere que puede pasar nullptrcomo argumento y está bien para la función. Eso es una especie de contrato entre el usuario y la implementación.

Klaim
fuente
49
No estoy seguro de que los punteros hagan que algo sea más difícil de leer. Es un concepto bastante simple y deja en claro cuándo es probable que algo se modifique. Si algo diría que es más difícil de leer cuando no hay indicios de lo que está sucediendo, ¿por qué add_one(a)no debería devolver el resultado, en lugar de establecerlo como referencia?
Connec
46
@connec: Si add_one(a)es confuso, entonces eso se debe a un nombre incorrecto. add_one(&a)tendría la misma confusión, solo que ahora podría estar incrementando el puntero y no el objeto. add_one_inplace(a)Evitaría toda confusión.
Nicol Bolas
20
Un punto, las referencias pueden referirse a la memoria que puede desaparecer tan fácilmente como los punteros. Por lo tanto, no son necesariamente más seguros que los punteros. Las referencias persistentes y pasajeras pueden ser tan peligrosas como los punteros.
Doug T.
66
@Klaim quise decir punteros en bruto. Quiero decir que C ++ tiene punteros, NULLy nullptr, y se les tiene por una razón. Y no es un consejo bien considerado o incluso realista dar que "nunca use punteros", y / o "nunca use NULL, siempre use boost::optional". Eso es una locura. No me malinterpreten, los punteros en bruto se necesitan con menos frecuencia en C ++ que en C, pero aún así, son útiles, no son tan "peligrosos" como a algunas personas de C ++ les gusta decir (eso también es una exageración), y de nuevo: cuando es más fácil usar un puntero e return nullptr;indicar un valor perdido ... ¿Por qué importar todo el Boost?
55
@Klaim "usar NULL es una mala práctica", ahora eso es simplemente ridículo. Y ifes obsoleto y uno debería usarlo while() { break; }, ¿verdad? Además, no se preocupe, he visto y trabajado con bases de código grandes, y sí, si es descuidado , entonces la propiedad es un problema. No obstante, si te apegas a las convenciones, úsalas de manera consistente y comenta y documenta tu código. Pero después de todo, debería usar C porque soy demasiado tonto para C ++, ¿verdad?
62

Las actuaciones son exactamente las mismas, ya que las referencias se implementan internamente como punteros. Por lo tanto, no necesita preocuparse por eso.

No existe una convención generalmente aceptada con respecto a cuándo utilizar referencias y punteros. En algunos casos, debe devolver o aceptar referencias (constructor de copias, por ejemplo), pero aparte de eso, puede hacer lo que desee. Una convención bastante común que he encontrado es usar referencias cuando el parámetro debe hacer referencia a un objeto existente y punteros cuando un valor NULL está bien.

Algunas convenciones de codificación (como la de Google ) prescriben que siempre se deben usar punteros o referencias constantes, porque las referencias tienen un poco de sintaxis clara: tienen un comportamiento de referencia pero sintaxis de valor.

Andrea Bergia
fuente
10
Solo para agregar un poco más a esto, la guía de estilo de Google dice que los parámetros de entrada a las funciones deben ser referencias constantes y las salidas deben ser punteros. Me gusta esto porque deja muy claro cuando lee una firma de función qué es una entrada y qué es una salida.
Dan
44
@Dan: La guía de estilo de Google es para el código antiguo de Google y no debe usarse para la codificación moderna. De hecho, es un estilo de codificación bastante malo para un nuevo proyecto.
GManNickG
13
@connec: Déjame ponerlo de esta manera: nulo es un valor de puntero perfectamente válido . En cualquier lugar donde haya un puntero, puedo darle el valor nulo. Ergo, tu segunda versión de add_oneestá rota :, add_one(0); // passing a perfectly valid pointer valuekaboom. Debe verificar si es nulo. Algunas personas responderán: "bueno, simplemente documentaré que mi función no funciona con nulo". Eso está bien, pero luego rechazas el propósito de la pregunta: si vas a mirar la documentación para ver si nulo está bien, también verás la declaración de la función .
GManNickG
8
Si fuera una referencia, verías que ese es el caso. Sin embargo, tal réplica pierde el punto: las referencias imponen en un nivel de lenguaje que se refiere a un objeto existente, y no posiblemente nulo, mientras que los punteros no tienen dicha restricción. Creo que está claro que la aplicación a nivel de lenguaje es más poderosa y menos propensa a errores que la aplicación a nivel de documentación. Algunos intentarán responder a esto diciendo: "Mira, referencia nula:. int& i = *((int*)0);Esta no es una réplica válida. El problema en el código anterior radica en el uso del puntero, no con la referencia . Las referencias nunca son nulas, punto.
GManNickG
12
Hola, vi falta de abogados de idioma en los comentarios, así que permítanme remediar: las referencias generalmente son implementadas por punteros, pero el estándar no dice tal cosa. Una implementación utilizando algún otro mecanismo sería 100% de queja.
Thomas Bonini
34

De C ++ FAQ Lite -

Utilice referencias cuando pueda y punteros cuando sea necesario.

Por lo general, se prefieren las referencias a los punteros cuando no necesita "volver a colocar". Esto generalmente significa que las referencias son más útiles en la interfaz pública de una clase. Las referencias generalmente aparecen en la piel de un objeto y los punteros en el interior.

La excepción a lo anterior es cuando el parámetro o el valor de retorno de una función necesita una referencia "centinela", una referencia que no hace referencia a un objeto. Esto suele hacerse mejor devolviendo / tomando un puntero y dándole al puntero NULL este significado especial (las referencias siempre deben alias de los objetos, no un puntero NULL desreferenciado).

Nota: A los programadores de la línea C antigua a veces no les gustan las referencias, ya que proporcionan una semántica de referencia que no está explícita en el código de la persona que llama. Sin embargo, después de cierta experiencia en C ++, uno se da cuenta rápidamente de que se trata de una forma de ocultar información, que es un activo más que un pasivo. Por ejemplo, los programadores deben escribir el código en el idioma del problema en lugar del idioma de la máquina.

Mahesh
fuente
1
Supongo que podría argumentar que si está utilizando una API, debe estar familiarizado con lo que hace y saber si el parámetro pasado se modifica o no ... algo a considerar, pero estoy de acuerdo con los programadores de C ( aunque tengo poca experiencia en C). Sin embargo, agregaría que una sintaxis más clara es beneficiosa tanto para los programadores como para las máquinas.
Connec
1
@connec: Seguro que el programador C lo tiene correcto para su lenguaje. Pero no cometa el error de tratar C ++ como C. Es un lenguaje completamente diferente. Si trata a C ++ como C, termina escribiendo lo que se denomina coequally como C with class(que no es C ++).
Martin York
15

Mi regla de oro es:

  • Use punteros para los parámetros salientes o entrantes / salientes. Por lo tanto, se puede ver que el valor se va a cambiar. (Debes usar &)
  • Utilice punteros si el parámetro NULL es un valor aceptable. (Asegúrese de que sea constun parámetro entrante)
  • Utilice referencias para el parámetro entrante si no puede ser NULL y no es un tipo primitivo ( const T&).
  • Utilice punteros o punteros inteligentes al devolver un objeto recién creado.
  • Utilice punteros o punteros inteligentes como estructura o miembros de clase en lugar de referencias.
  • Use referencias para aliasing (ej. int &current = someArray[i])

Independientemente de cuál use, no olvide documentar sus funciones y el significado de sus parámetros si no son obvios.

Calmarius
fuente
14

Descargo de responsabilidad: aparte del hecho de que las referencias no pueden ser NULL ni "rebotar" (lo que significa que no pueden cambiar el objeto del que son el alias), realmente se trata de una cuestión de gustos, así que no voy a decir "este es mejor".

Dicho esto, no estoy de acuerdo con su última declaración en la publicación, ya que no creo que el código pierda claridad con las referencias. En tu ejemplo

add_one(&a);

podría ser más claro que

add_one(a);

ya que sabes que lo más probable es que el valor de a cambie. Por otro lado, sin embargo, la firma de la función

void add_one(int* const n);

de alguna manera tampoco está claro: ¿n va a ser un solo entero o una matriz? A veces solo tienes acceso a encabezados (mal documentados) y firmas como

foo(int* const a, int b);

No son fáciles de interpretar a primera vista.

En mi opinión, las referencias son tan buenas como los punteros cuando no se necesita (re) asignación ni reenlace (en el sentido explicado anteriormente). Además, si un desarrollador solo usa punteros para matrices, las firmas de funciones son algo menos ambiguas. Sin mencionar el hecho de que la sintaxis de los operadores es mucho más legible con referencias.

bartgol
fuente
Gracias por la clara demostración de dónde ambas soluciones ganan y pierden claridad. Inicialmente estaba en el campamento de punteros, pero esto tiene mucho sentido.
Zach Beavon-Collin
12

Como otros ya respondieron: siempre use referencias, a menos que la variable sea NULL/ nullptrsea realmente un estado válido.

El punto de vista de John Carmack sobre el tema es similar:

Los punteros NULL son el mayor problema en C / C ++, al menos en nuestro código. El uso dual de un solo valor como indicador y dirección causa una increíble cantidad de problemas fatales. Las referencias de C ++ se deben favorecer sobre los punteros siempre que sea posible; mientras que una referencia es "realmente" solo un puntero, tiene el contrato implícito de no ser NULL. Realice comprobaciones NULL cuando los punteros se conviertan en referencias, luego puede ignorar el problema a partir de entonces.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Editar 2012-03-13

El usuario Bret Kuhns comenta correctamente:

El estándar C ++ 11 ha sido finalizado. Creo que es hora de mencionar en este hilo que la mayoría del código debería funcionar perfectamente con una combinación de referencias, shared_ptr y unique_ptr.

Es cierto, pero la pregunta sigue siendo, incluso cuando se reemplazan los punteros sin formato con punteros inteligentes.

Por ejemplo, ambos std::unique_ptry std::shared_ptrse pueden construir como punteros "vacíos" a través de su constructor predeterminado:

... lo que significa que usarlos sin verificar que no están vacíos conlleva el riesgo de un accidente, que es exactamente de lo que trata la discusión de J. Carmack.

Y luego, tenemos el divertido problema de "¿cómo pasamos un puntero inteligente como parámetro de función?"

La respuesta de Jon a la pregunta C ++: pasar referencias a boost :: shared_ptr , y los siguientes comentarios muestran que incluso entonces, pasar un puntero inteligente por copia o por referencia no es tan claro como quisiera (me favorezco el " por referencia "por defecto, pero podría estar equivocado).

paercebal
fuente
1
El estándar C ++ 11 ha sido finalizado. Creo que es hora de mencionar en este hilo que la mayoría del código debería funcionar perfectamente con una combinación de referencias shared_ptr, y unique_ptr. La semántica de propiedad y las convenciones de parámetros de entrada / salida se ocupan de una combinación de estas tres piezas y constancia. Casi no hay necesidad de punteros sin formato en C ++, excepto cuando se trata de código heredado y algoritmos muy optimizados. Las áreas donde se usan deben estar tan encapsuladas como sea posible y convertir cualquier puntero sin procesar al equivalente semántico apropiado "moderno".
Bret Kuhns
1
La mayoría de las veces, los punteros inteligentes no se deben pasar, sino que se debe probar la nulidad y luego se debe pasar su objeto contenido por referencia. El único momento en el que debería pasar un puntero inteligente es cuando transfiere (unique_ptr) o comparte (shared_ptr) la propiedad con otro objeto.
Luke Worth
@povman: Estoy totalmente de acuerdo: si la propiedad no es parte de la interfaz (y a menos que esté a punto de modificarse, no debería serlo), entonces no deberíamos pasar un puntero inteligente como parámetro (o valor de retorno). La cosa se vuelve un poco más complicada cuando la propiedad es parte de la interfaz. Por ejemplo, el debate de Sutter / Meyers sobre cómo pasar un unique_ptr como parámetro: ¿por copia (Sutter) o por referencia de valor r (Meyers)? Un anti patrón se basa en pasar alrededor de un puntero a una shared_ptr mundial, con el riesgo de que el puntero de ser invalidada (la solución es copiar el puntero inteligente en la pila)
paercebal
7

No es cuestión de gustos. Aquí hay algunas reglas definitivas.

Si desea hacer referencia a una variable declarada estáticamente dentro del alcance en el que se declaró, utilice una referencia de C ++ y será perfectamente segura. Lo mismo se aplica a un puntero inteligente declarado estáticamente. Pasar parámetros por referencia es un ejemplo de este uso.

Si desea hacer referencia a cualquier cosa desde un alcance que sea más amplio que el alcance en el que se declara, entonces debe usar un puntero inteligente de referencia para que sea perfectamente seguro.

Puede referirse a un elemento de una colección con una referencia por conveniencia sintáctica, pero no es seguro; El elemento se puede eliminar en cualquier momento.

Para mantener de forma segura una referencia a un elemento de una colección, debe usar un puntero inteligente de referencia contada.

John Morrison
fuente
5

Cualquier diferencia de rendimiento sería tan pequeña que no justificaría usar el enfoque que es menos claro.

Primero, un caso que no se mencionó donde las referencias son generalmente superiores son las constreferencias. Para los tipos no simples, pasar un const referenceevita crear un temporal y no causa la confusión que le preocupa (porque el valor no se modifica). Aquí, obligar a una persona a pasar un puntero causa la confusión que le preocupa, ya que ver la dirección tomada y pasada a una función puede hacerle pensar que el valor cambió.

En cualquier caso, básicamente estoy de acuerdo contigo. No me gustan las funciones que toman referencias para modificar su valor cuando no es muy obvio que esto es lo que está haciendo la función. Yo también prefiero usar punteros en ese caso.

Cuando necesita devolver un valor en un tipo complejo, tiendo a preferir referencias. Por ejemplo:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Aquí, el nombre de la función deja en claro que está recuperando información en una matriz. Entonces no hay confusión.

Las principales ventajas de las referencias son que siempre contienen un valor válido, son más limpios que los punteros y admiten el polimorfismo sin necesidad de ninguna sintaxis adicional. Si ninguna de estas ventajas se aplica, no hay razón para preferir una referencia sobre un puntero.

David Schwartz
fuente
4

Copiado de wiki -

Una consecuencia de esto es que en muchas implementaciones, operar en una variable con una vida útil automática o estática a través de una referencia, aunque sintácticamente similar a acceder directamente a ella, puede involucrar operaciones de desreferencia ocultas que son costosas. Las referencias son una característica sintácticamente controvertida de C ++ porque oscurecen el nivel de indirección de un identificador; es decir, a diferencia del código C donde los punteros generalmente se destacan sintácticamente, en un bloque grande de código C ++ puede no ser inmediatamente obvio si el objeto al que se accede se define como una variable local o global o si es una referencia (puntero implícito) a alguna otra ubicación, especialmente si el código mezcla referencias e indicadores. Este aspecto puede hacer que el código C ++ mal escrito sea más difícil de leer y depurar (vea Aliasing).

Estoy 100% de acuerdo con esto, y es por eso que creo que solo debes usar una referencia cuando tienes una muy buena razón para hacerlo.

usuario606723
fuente
También estoy de acuerdo en gran medida, sin embargo, estoy llegando a la conclusión de que la pérdida de la protección integrada contra punteros NULL es demasiado costosa para preocupaciones puramente sintácticas, especialmente porque, aunque más explícita, la sintaxis de puntero es bastante fea de todas formas.
Connec
Supongo que la circunstancia también sería un factor importante. Creo que tratar de usar referencias cuando la base de código actual utiliza predominantemente punteros sería una mala idea. Si esperas que sean referencias, entonces el hecho de que sea tan implícito es menos importante tal vez ...
user606723
3

Puntos a tener en cuenta:

  1. Los punteros pueden ser NULL, las referencias no pueden ser NULL.

  2. Las referencias son más fáciles de usar, constse pueden usar como referencia cuando no queremos cambiar el valor y solo necesitamos una referencia en una función.

  3. Puntero usado con un *tiempo referencias usadas con a &.

  4. Utilice punteros cuando se requiera la operación aritmética del puntero.

  5. Puede tener punteros a un tipo vacío int a=5; void *p = &a;pero no puede tener una referencia a un tipo vacío.

Referencia de puntero vs

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Veredicto cuando usar qué

Puntero : para arreglos, lista de enlaces, implementaciones de árbol y aritmética de puntero.

Referencia : en parámetros de función y tipos de retorno.

shaurya uppal
fuente
2

Hay un problema con la regla de " usar referencias siempre que sea posible " y surge si desea mantener la referencia para un uso posterior. Para ilustrar esto con un ejemplo, imagine que tiene las siguientes clases.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

Al principio, puede parecer una buena idea tener un parámetro en el RefPhone(const SimCard & card)constructor pasado por una referencia, porque evita pasar punteros incorrectos / nulos al constructor. De alguna manera, fomenta la asignación de variables en la pila y aprovecha los beneficios de RAII.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Pero luego los temporales vienen a destruir tu mundo feliz.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

Entonces, si se apega ciegamente a las referencias, intercambia la posibilidad de pasar punteros inválidos por la posibilidad de almacenar referencias a objetos destruidos, lo que básicamente tiene el mismo efecto.

editar: Tenga en cuenta que me apegué a la regla "Utilice la referencia siempre que pueda, los punteros donde sea necesario. Evite los punteros hasta que no pueda". de la respuesta más votada y aceptada (otras respuestas también lo sugieren). Aunque debería ser obvio, el ejemplo no es mostrar que las referencias como tales son malas. Sin embargo, pueden ser mal utilizados, al igual que los punteros, y pueden traer sus propias amenazas al código.


Existen las siguientes diferencias entre punteros y referencias.

  1. Cuando se trata de pasar variables, pasar por referencia parece pasar por valor, pero tiene semántica de puntero (actúa como puntero).
  2. La referencia no se puede inicializar directamente a 0 (nulo).
  3. La referencia (referencia, objeto no referenciado) no se puede modificar (equivalente al puntero "* const").
  4. La referencia constante puede aceptar parámetros temporales.
  5. Las referencias locales constantes prolongan la vida útil de los objetos temporales

Teniendo esto en cuenta, mis reglas actuales son las siguientes.

  • Utilice referencias para parámetros que se utilizarán localmente dentro de un alcance de función.
  • Utilice punteros cuando 0 (nulo) sea un valor de parámetro aceptable o necesite almacenar parámetros para un uso posterior. Si 0 (nulo) es aceptable, estoy agregando el sufijo "_n" al parámetro, use un puntero protegido (como QPointer en Qt) o simplemente documente. También puede usar punteros inteligentes. Debe ser aún más cuidadoso con los punteros compartidos que con los punteros normales (de lo contrario, puede terminar con fugas de memoria de diseño y desorden de responsabilidad).
Doc
fuente
3
El problema con su ejemplo no es que las referencias no sean seguras, sino que confía en algo fuera del alcance de su instancia de objeto para mantener vivos a sus miembros privados. const SimCard & m_card;es solo un código mal escrito.
plamenko
@plamenko Tengo miedo de que no entiendas el propósito del ejemplo. Si const SimCard & m_cardes correcto o no depende del contexto. El mensaje en esta publicación no es que las referencias no sean seguras (aunque pueden serlo si uno se esfuerza mucho). El mensaje es que no debe ceñirse ciegamente al mantra "usar referencias siempre que sea posible". El ejemplo es el resultado del uso agresivo de la doctrina de "usar referencias siempre que sea posible". Esto debería estar claro.
doc
Hay dos cosas que me molestan con su respuesta porque creo que puede inducir a error a alguien que intenta aprender más sobre el asunto. 1. La publicación es unidireccional y es fácil tener la impresión de que las referencias son malas. Solo proporcionó un solo ejemplo de cómo no utilizar referencias. 2. No estabas claro en tu ejemplo qué tiene de malo. Sí, lo temporal se destruirá, pero no fue esa línea la que estuvo mal, es la implementación de la clase.
plamenko
Prácticamente nunca deberías tener miembros como const SimCard & m_card. Si desea ser eficiente con los temporales, agregue el explicit RefPhone(const SimCard&& card)constructor.
plamenko
@plamenko si no puedes leer con un poco de comprensión básica, entonces tienes un problema mayor que simplemente ser engañado por mi publicación. No sé cómo podría ser más claro. Mira la primera oración. ¡Hay un problema con el mantra "usar referencias siempre que sea posible"! ¿Dónde en mi publicación has encontrado una declaración de que las referencias son malas? Al final de mi publicación, ha escrito dónde usar referencias, entonces, ¿cómo llegó a tales conclusiones? ¿Esta no es una respuesta directa a la pregunta?
doc
1

Las siguientes son algunas pautas.

Una función usa datos pasados ​​sin modificarlos:

  1. Si el objeto de datos es pequeño, como un tipo de datos incorporado o una estructura pequeña, páselo por valor.

  2. Si el objeto de datos es una matriz, use un puntero porque esa es su única opción. Haga que el puntero sea un puntero para const.

  3. Si el objeto de datos es una estructura de buen tamaño, use un puntero constante o una referencia constante para aumentar la eficiencia del programa. Ahorre el tiempo y el espacio necesarios para copiar una estructura o un diseño de clase. Haga el puntero o la referencia const.

  4. Si el objeto de datos es un objeto de clase, use una referencia constante. La semántica del diseño de clase a menudo requiere el uso de una referencia, que es la razón principal por la que C ++ agregó esta característica. Por lo tanto, la forma estándar de pasar argumentos de objetos de clase es por referencia.

Una función modifica datos en la función de llamada:

1. Si el objeto de datos es un tipo de datos incorporado, use un puntero. Si detecta código como fixit (& x), donde x es un int, está bastante claro que esta función tiene la intención de modificar x.

2. Si el objeto de datos es una matriz, use su única opción: un puntero.

3. Si el objeto de datos es una estructura, use una referencia o un puntero.

4. Si el objeto de datos es un objeto de clase, use una referencia.

Por supuesto, estas son solo pautas, y puede haber razones para tomar diferentes decisiones. Por ejemplo, cin usa referencias para tipos básicos para que pueda usar cin >> n en lugar de cin >> & n.

Sachin Godara
fuente
0

Las referencias son más limpias y fáciles de usar, y hacen un mejor trabajo al ocultar información. Sin embargo, las referencias no pueden reasignarse. Si necesita apuntar primero a un objeto y luego a otro, debe usar un puntero. Las referencias no pueden ser nulas, por lo que si existe alguna posibilidad de que el objeto en cuestión pueda ser nulo, no debe usar una referencia. Debes usar un puntero. Si desea manejar la manipulación de objetos por su cuenta, es decir, si desea asignar espacio de memoria para un objeto en el montón en lugar de en la pila, debe usar el puntero

int *pInt = new int; // allocates *pInt on the Heap
Shashikant Mitkari
fuente
0

El ejemplo escrito correctamente debería verse así

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

Es por eso que las referencias son preferibles si es posible ...

Slavenskij
fuente
-1

Solo pongo mi moneda de diez centavos. Acabo de realizar una prueba. Un estornudo por eso. Solo dejo que g ++ cree los archivos de ensamblaje del mismo mini programa usando punteros en comparación con el uso de referencias. Al mirar la salida, son exactamente iguales. Aparte de la simbolización. En cuanto al rendimiento (en un ejemplo simple) no hay problema.

Ahora sobre el tema de punteros vs referencias. En mi humilde opinión, creo que la claridad está por encima de todo. Tan pronto como leo el comportamiento implícito, mis dedos comienzan a curvarse. Estoy de acuerdo en que es un buen comportamiento implícito que una referencia no puede ser NULL.

Desreferenciar un puntero NULL no es el problema. bloqueará su aplicación y será fácil de depurar. Un problema mayor son los punteros no inicializados que contienen valores no válidos. Lo más probable es que esto provoque daños en la memoria que causen un comportamiento indefinido sin un origen claro.

Aquí es donde creo que las referencias son mucho más seguras que los punteros. Y estoy de acuerdo con una declaración anterior, que la interfaz (que debe estar claramente documentada, ver diseño por contrato, Bertrand Meyer) define el resultado de los parámetros de una función. Ahora, teniendo todo esto en cuenta, mis preferencias van a usar referencias donde sea / cuando sea posible.

Tom van den Broek
fuente
-2

Para los punteros, necesita que apunten a algo, por lo que los punteros cuestan espacio de memoria.

Por ejemplo, una función que toma un puntero entero no tomará la variable entera. Por lo tanto, deberá crear un puntero para que ese primero pase a la función.

En cuanto a una referencia, no costará memoria. Tiene una variable entera y puede pasarla como una variable de referencia. Eso es. No necesita crear una variable de referencia especialmente para ella.

Rabbiya Shahid
fuente
44
No Una función que toma un puntero no requiere la asignación de una variable de puntero: puede pasar un temporal &address. Una referencia ciertamente costará memoria si es miembro de un objeto, y además, todos los compiladores existentes implementan referencias como direcciones, por lo que tampoco ahorra nada en términos de pasar parámetros o desreferenciarlos.
underscore_d