¿Por qué C ++ y Java usan la noción de "referencia" pero no en el mismo sentido?

26

En C ++, un argumento de referencia a una función permite que la función haga que la referencia se refiera a otra cosa:

int replacement = 23;

void changeNumberReference(int& reference) {
    reference = replacement;
}

int main() {
    int i = 1;
    std::cout << "i=" << i << "\n"; // i = 1;
    changeNumberReference(i);
    std::cout << "i=" << i << "\n"; // i = 23;
}

Análogamente, un argumento de referencia constante a una función arrojará un error de tiempo de compilación si intentamos cambiar la referencia:

void changeNumberReference(const int& reference) {
    reference = replacement; // compile-time error: assignment of read-only reference 'reference'
}

Ahora, con Java, los documentos dicen que los argumentos de funciones de tipos no primitivos son referencias. Ejemplo de los documentos oficiales:

public void moveCircle(Circle circle, int deltaX, int deltaY) {
    // code to move origin of circle to x+deltaX, y+deltaY
    circle.setX(circle.getX() + deltaX);
    circle.setY(circle.getY() + deltaY);

    // code to assign a new reference to circle
    circle = new Circle(0, 0);
}

Luego, al círculo se le asigna una referencia a un nuevo objeto Circle con x = y = 0. Sin embargo, esta reasignación no tiene permanencia porque la referencia se pasó por valor y no puede cambiar.

Para mí, esto no se parece en nada a las referencias de C ++. No se parece a las referencias regulares de C ++ porque no puede hacer que se refiera a otra cosa, y no se parece a las referencias constantes de C ++ porque en Java, el código que cambiaría (pero realmente no) la referencia no arroja una compilación error de tiempo

Esto es más similar en comportamiento a los punteros de C ++. Puede usarlo para cambiar los valores de los objetos puntiagudos, pero no puede cambiar el valor del puntero en una función. Además, como con los punteros de C ++ (pero no con las referencias de C ++), en Java puede pasar "nulo" como valor para dicho argumento.

Entonces mi pregunta es: ¿Por qué Java usa la noción de "referencia"? ¿Debe entenderse que no se parecen a las referencias de C ++? ¿O realmente se parecen a las referencias de C ++ y me falta algo?

Shivan Dragon
fuente
10
Tenga en cuenta que en su primer ejemplo de C ++, no está haciendo que la referencia "señale a otra cosa". En cambio, está cambiando el valor de la variable a la que apunta la referencia. Esto es conceptualmente similar a mutar el estado del objeto al que se hace referencia con la referencia de Java,
Xion
@ Xion sí, ahora que he leído tu comentario, veo lo que quieres decir y estoy de acuerdo con el 98% de lo que dices :) El problema en Java es que debes tener acceso a todos y cada uno de los estados del objeto para poder conceptualmente lograr lo que C ++ simplemente hace al permitirle establecer la referencia al valor de alguna otra referencia.
Shivan Dragon

Respuestas:

48

¿Por qué? Porque, aunque una terminología consistente es generalmente buena para toda la profesión, los diseñadores de idiomas no siempre respetan el uso del idioma de otros diseñadores de idiomas, particularmente si esos otros idiomas son percibidos como competidores.

Pero realmente, ninguno de los dos usos de 'referencia' fue una muy buena elección. Las "referencias" en C ++ son simplemente una construcción de lenguaje para introducir alias (nombres alternativos para exactamente la misma entidad) explícitamente. Las cosas habrían sido mucho más claras, simplemente habían llamado a la nueva característica "alias" en primer lugar. Sin embargo, en ese momento la gran dificultad era hacer que todos entendieran la diferencia entre los punteros (que requieren desreferenciación) y las referencias (que no), por lo que lo importante era que se llamaba algo más que "puntero", y no así mucho más específicamente qué término usar.

Java no tiene punteros, y está orgulloso de ello, por lo que usar "puntero" como término no era una opción. Sin embargo, las "referencias" que tiene se comportan bastante como los punteros de C ++ cuando los pasas - la gran diferencia es que no puedes hacer las más desagradables operaciones de bajo nivel (lanzar, agregar ...) en ellos , pero dan como resultado exactamente la misma semántica cuando pasa identificadores a entidades que son idénticas frente a entidades que simplemente son iguales. Desafortunadamente, el término "puntero" lleva tantas asociaciones negativas de bajo nivel que es poco probable que la comunidad Java lo acepte.

El resultado es que ambos idiomas usan el mismo término vago para dos cosas bastante diferentes, las cuales podrían beneficiarse de un nombre más específico, pero ninguno de los cuales es probable que sea reemplazado en el corto plazo. ¡El lenguaje natural también puede ser frustrante a veces!

Kilian Foth
fuente
77
Gracias, pensar en las referencias de C ++ como "alias" (para el mismo valor / instancia) y las referencias de Java como "punteros sin las operaciones más desagradables de bajo nivel (conversión, adición ...)" realmente me aclara las cosas.
Shivan Dragon
14
Java no tiene punteros, y está orgulloso de ello, por lo que usar "puntero" como término no era una opción. ¿Qué pasa con NullPointerExceptionel hecho de que el JLS utiliza el término "puntero"?
Tobias Brandt
77
@TobiasBrandt: NullPointerExceptionno es un puntero, pero es una excepción que indica que, en un nivel inferior, se pasó / desreferenciado un puntero NULL. El uso Pointercomo parte de una excepción, en todo caso, se suma a la imagen desagradable que tienen. El JLS tiene que mencionar los punteros, porque la VM de Java se escribió principalmente en un lenguaje que los usa. No se pueden describir con precisión las especificaciones del lenguaje sin describir cómo funcionan las
partes
3
@TobiasBrandt: Java usa punteros por todas partes; Se hace referencia a los objetos del montón a través de algo que, a todos los efectos prácticos, es un puntero. Es solo que Java no expone ninguna operación en tipos de puntero al programador.
John Bode
2
Prefiero el término "ID de objeto" para referencias Java / .NET. A nivel de bit, tales cosas pueden implementarse típicamente como algo parecido a un puntero, pero nada requiere que lo sean. Lo importante es que una ID de objeto contiene cualquier tipo de información que el framework / vm necesita para identificar un objeto.
Supercat
9

Una referencia es una cosa que se refiere a otra cosa. Varios idiomas han atribuido un significado más específico a la palabra "referencia", generalmente a "algo así como un puntero sin todos los aspectos negativos". C ++ atribuye un significado específico, como lo hacen Java o Perl.

En C ++, las referencias son más como alias (que se pueden implementar a través de un puntero). Esto permite pasar por referencia o fuera de los argumentos .

En Java, las referencias son punteros, excepto que este no es un concepto reificado del lenguaje: todos los objetos son referencias, los tipos primitivos como los números no lo son. No quieren decir "puntero" porque no hay aritmética de puntero y no hay punteros reificados en Java, pero quieren dejar en claro que cuando se pasa Objectun argumento, el objeto no se copia . Esto tampoco es pasar por referencia , sino algo más como pasar por compartir .

amon
fuente
6

En gran parte se remonta a Algol 68, y en parte a una reacción contra la forma en que C define los punteros.

Algol 68 definió un concepto llamado referencia. Era casi lo mismo que (por ejemplo) un puntero en Pascal. Era una celda que contenía NIL o la dirección de alguna otra celda de algún tipo especificado. Puede asignar a una referencia, por lo que una referencia podría referirse a una celda a la vez y a una celda diferente después de ser reasignada. Lo hizo no obstante, el apoyo nada análogo a C o C ++ aritmética de punteros.

Al menos como Algol 68 definió las cosas, sin embargo, las referencias eran un concepto bastante limpio que era bastante torpe para usar en la práctica. La mayoría de las definiciones de variables eran en realidad referencias, por lo que definieron una notación abreviada para evitar que se descontrolara por completo, pero cualquier uso más que trivial podría volverse torpe bastante rápido de todos modos.

Por ejemplo, una declaración como INT j := 7;fue realmente tratada por el compilador como una declaración como REF INT j = NEW LOC INT := 7. Entonces, lo que declaró en Algol 68 era normalmente una referencia, que luego se inicializó para referirse a algo que se asignó en el montón, y que se inicializó (opcionalmente) para contener algún valor especificado. Sin embargo, a diferencia de Java, al menos trataron de permitirle mantener una sintaxis sensata para eso en lugar de tener constantemente cosas como foo bar = new foo();o tratar de decir mentiras sobre "nuestros punteros no son punteros".

Pascal y la mayoría de sus descendientes (tanto directos como ... espirituales) cambiaron el nombre del concepto de referencia Algol 68 por "puntero", pero mantuvieron el concepto esencialmente igual: un puntero era una variable que contenía nil, o la dirección de algo que asignó en el montón (es decir, al menos como lo definieron originalmente Jensen y Wirth, no había un operador de "dirección de", por lo que no había forma de que un puntero se refiriera a una variable normalmente definida). A pesar de ser "punteros", no se admitía ninguna aritmética de punteros.

C y C ++ añadieron algunos giros a eso. En primer lugar, hacer tener un operador de dirección, por lo que un puntero puede referirse no sólo a algo asignada en el montón, sino para cualquier variable, independientemente de cómo se asigna. En segundo lugar, definen la aritmética en punteros, y definen los subíndices de matriz como básicamente una simple notación abreviada de aritmética de puntero, por lo que la aritmética de puntero es omnipresente (casi inevitable) en la mayoría de C y C ++.

Cuando se estaba inventando Java, Sun aparentemente pensó que "Java no tiene punteros" era un mensaje de marketing más simple y limpio que: "casi todo en Java es un puntero, pero estos punteros son principalmente como Pascal en lugar de C". Como habían decidido que "puntero" no era un término aceptable, necesitaban algo más, y desenterraron "referencia", a pesar de que sus referencias eran sutiles (y en algunos casos no tan sutiles) diferentes de las referencias de Algol 68.

Aunque salió algo diferente, C ++ estaba atrapado con aproximadamente el mismo problema: la palabra "puntero" ya era conocida y entendida, por lo que necesitaban una palabra diferente para esta cosa diferente que estaban agregando que se refería a otra cosa, pero por lo demás era bastante un poco diferente de lo que la gente entendió que significa "puntero". Entonces, aunque también es notablemente diferente de una referencia de Algol 68, también reutilizaron el término "referencia".

Jerry Coffin
fuente
Lo que hace que la referencia en Algol 68 sea particularmente dolorosa es la desreferencia automática. Eso es algo conveniente siempre que esté limitado a un nivel. Pero el hecho de que se pudiera hacer varias veces combinado con el azúcar sintáctico que ocultaba algunas de las referencias a los ojos inconscientes lo hizo confuso.
Programador
0

La noción de 'tipo de referencia' es genérica, puede expresar tanto un puntero como una referencia (en términos de C ++), en oposición al 'tipo de valor'. Las referencias de Java son realmente punteros sin sobrecarga sintáctica de ->una *desreferenciación. Si observa la implementación de JVM en C ++, son realmente punteros. Además, C # tiene una noción de referencias similar a la de Java, pero también tiene punteros y calificadores 'ref' en los parámetros de la función, lo que permite pasar tipos de valores por referencia y evitar la copia como &en C ++.

Hertz
fuente
¿Cómo es esta respuesta diferente / mejor de las respuestas anteriores aquí?
mosquito el