2012-03-16 13 views
12

C++ FAQ dice:¿Es realmente la mejor práctica preferir las referencias de C++ a los punteros en todas las situaciones?

“Utilizar referencias cuando se puede, y los punteros cuando tienen que hacerlo.”

Siento que no es tan fácil como se indicó anteriormente. Las razones por las que las referencias son generalmente mejores para los indicadores han sido bien discutidas. Así que me gustaría discutir casos especiales donde las referencias tienen sentido pero son muy inusuales y simplemente no me siento bien:

Para mí, el caso más obvio es su uso en estructuras de datos generales como un árbol o lista. Sus interfaces podrían ciertamente implementan utilizando referencias y sentirse más seguro, pero no he visto una sola aplicación que tomar ventaja de las referencias por ejemplo:

Node *parent = n.parent(); // parent might be unset -> pointer 
Node &child = n.child(5); // child is always valid -> reference 

Aunque podría parecer atractiva, que conduce a un código como éste:

if(parent == &child) ... // weird, unusual at least? 

Me pregunto, ¿esto es lo que significa pure C++?

Cada vez que he intentado utilizar ciegamente referencias allí donde es posible, me he encontrado todo tipo de inconsistencias similares y terminó punteros de mezcla y referencias de diversas maneras feas.

Para concluir, ¿por qué el operador nuevo no devuelve en primer lugar una referencia a una nueva instancia en lugar de un puntero? (Y lanza en caso de error):

Node &n = new Node; 
delete &n; 

Es un ejemplo absurdo, pero no es que lo que sería “C++ puro”?

+6

Te estás perdiendo el punto. Piense en la cita como "use almacenamiento automático cuando pueda, y almacenamiento dinámico (' nuevo') cuando sea necesario ". – ildjarn

+2

Use lo que tenga sentido. Por lo general, eso significa punteros en la implementación de contenedores y referencias en el código del cliente. Además, nadie puede evitar que alguien escriba código ridículo, por lo que si alguien dice 'eliminar & x;', no hay mucho que pueda hacer. Debe suponer que sus clientes * desean * escribir programas correctos. Solo tienes que facilitarles las cosas. –

+0

Las referencias son preferidas al pasar objetos a funciones también porque es más rápido debido a que no se invoca un constructor de copia. – chris

Respuesta

-5

El operador "nuevo" asigna memoria (al igual que malloc en C). Como C++ no tiene implementado un recolector de basura (como Java o C#), puede quedarse sin memoria. Por lo tanto, cuando falla la asignación de memoria nueva, se devuelve un valor NULL. De esta manera puede probar si la asignación fue exitosa.

No se puede usar una referencia con el nuevo operador debido a la posibilidad de asignar el valor NULL debido a la falta de memoria.

+5

* Debido a que C++ no tiene implementado un recolector de basura (como Java o C#), puede quedarse sin memoria * [¿Implica que Java no puede quedarse sin memoria?] (Http://www.google.com.au/webhp) ? sourceid = chrome-instant & es = UTF-8 & ion = 1 # hl = en & sugexp = frgbld & gs_nf = 1 & cp = 14 & gs_id = 2 & xhr = t & q = java.lang.outofmemoryerror & pf = p & output = search & sclient = psy-ab & oq = java.lang.outo & aq = 0 & aqi = g4 & aql = & gs_sm = & gs_upl = & gs_l = & pbx = 1 & bav = on.2, or.r_gc.r_pw.r_qf., cf.osb & fp = 9ac572733e1be876 & biw = 1920 & bih = 979 & ion = 1) –

+4

'new' no devuelve NULL, arroja una excepción . –

+4

'new' no devuelve NULL, arroja' std :: bad_alloc'. Para que 'new' devuelva NULL en la falla de asignación, debe llamarlo con el parámetro' std :: nothrow'. – Praetorian

6

Creo que las estructuras de datos genéricas caen bajo la cláusula "use punteros cuando tenga que hacerlo". Es decir, dado que las referencias son constantes (es decir, siempre se refieren al mismo objeto), el uso de referencias en su lista vinculada o estructura de nodos significaría que no podría insertar nodos nuevos o eliminar los antiguos de la estructura de datos, generalmente derrotando el propósito.

Como alguien dijo en los comentarios, creo que los consejos realmente se aplican a los argumentos de la función. Pasar referencias en lugar de punteros evita tener que agregar lógica de manejo con puntero nulo a cada función, lo que trae una serie de beneficios en términos de tamaño y claridad del código.

+0

En el caso de un nodo de árbol, técnicamente puede utilizar referencias en la interfaz y aún así insertar y eliminar nodos mediante la manipulación de la representación interna que utiliza el puntero. Eso sería un poco extraño, pero aún posible. De lo contrario, estoy de acuerdo en que, en el contexto de los argumentos de función, las referencias tienen más sentido. – FipS

+1

Tener miembros de datos de referencia tiene el hábito de causar problemas para cualquier tipo que deba tratarse con semántica de valores, p. copiado –

+0

Sí, y cuando usa miembros de datos de puntero en lugar de referencias para permitir la semántica de copia. La pregunta es si seguir con punteros incluso en la interfaz o exponer los punteros como referencias. Personalmente, me quedaré con los indicadores, pero sospecho que la recomendación como saciado es usar referencias en este caso. – FipS

10

Una referencia no se puede modificar para hacer referencia a un objeto diferente una vez que se ha inicializado. Pero un puntero podría modificarse para referir diferentes objetos en tiempo de ejecución.

Una regla general

Cada vez que desee una variable para apuntar a diferentes objetos durante su tiempo de vida, el uso de un puntero.

Si solo quiere usar una variable como alias para otra variable, use una referencia.

como parámetros

veces Pasamos parámetros como referencias (const/no constante). Esto ayudaría a evitar copiar todo el objeto, cada vez que se llame a una función. Por lo tanto, esto tendría mucho sentido, al pasar objeto de gran tamaño.

Si la función necesita recibir algún parámetro que puede ser opcionalmente nulo, puede usar un puntero. Pero, nuevamente, siempre puedes definir un objeto nulo estático para cualquier clase, y puede usarse.

Como devolver valores

Siempre que los operadores de sobrecarga tales como [] o < <, que volvería referencias. Es porque, si digo, vec [0] = 10, en realidad debería asignar el valor 10 a la posición 0 en vec. Aquí no pude usar el puntero por razones obvias. * vec [0] = 10 no solo se verá bien. Además, debe garantizar que, cuando devuelve una referencia, siempre se refiere a un objeto (nunca debe ser nulo).

De nuevo, cuando la función puede devolver valores NULOS, use un puntero, de lo contrario, tendrá que declarar un objeto nulo estático para esa clase.

Como miembros de datos

referencias deben ser inicializado en el constructor. Por lo tanto, no se pueden cambiar una vez que se construye el objeto.

Por lo tanto, si necesita una variable que debe apuntar a diferentes objetos durante la vida útil de la clase, un puntero sería una buena opción.

+1

+1 Bienvenido a SO y felicidades por un buen debut;) –

+0

Gracias, Christian. –

1

Si está familiarizado con columnas NULL y NOT NULL en una base de datos relacional, o con la diferencia entre un elemento obligatorio y un elemento opcional en XSD, puede dibujar un paralelo útil de dicho dominio para guiarlo.

Las referencias representarían variables que son fijas, independientes y no aceptan nulos. Punteros para aquellos a los que realmente tiene una necesidad real de pasar "sin valor", o donde tiene una clara necesidad de realizar la aritmética del puntero (como con una matriz). Estas no son de ninguna manera las únicas diferencias, pero la mayoría de las diferentes sintaxis se siguen a partir de ahí.

Recapitular: reference = NOT NULL y read only. Puntero = flexible y un poco más peligroso.

Cuestiones relacionadas