c++11 - Mover la semántica en Golang
(3)
La especificación del lenguaje de programación Go
En una llamada a función, el valor y los argumentos de la función se evalúan en el orden habitual. Después de que se evalúan, los parámetros de la llamada pasan por valor a la función y la función llamada comienza a ejecutarse. Los parámetros de retorno de la función se pasan por valor de regreso a la función de llamada cuando la función retorna.
En Go, todo pasa por valor.
En Go, todo pasa por valor. Todo.
Hay algunos tipos (punteros, canales, mapas, sectores) que tienen propiedades similares a las de referencia, pero en esos casos la estructura de datos relevante (puntero, puntero de canal, encabezado de mapa, encabezado de sector) contiene un puntero a un objeto compartido subyacente ( cosa puntiaguda, descriptor de canal, tabla hash, matriz); la estructura de datos en sí misma se pasa por valor. Siempre.
Siempre.
-robar
Esto de Bjarne Stroustrup''s The C ++ Programming Language, Cuarta Edición 3.3.2.
Realmente no queríamos una copia; solo queríamos obtener el resultado de una función: queríamos mover un Vector en lugar de copiarlo. Afortunadamente, podemos expresar esa intención:
class Vector { // ... Vector(const Vector& a); // copy constructor Vector& operator=(const Vector& a); // copy assignment Vector(Vector&& a); // move constructor Vector& operator=(Vector&& a); // move assignment };
Dada esa definición, el compilador elegirá el constructor de movimiento para implementar la transferencia del valor de retorno fuera de la función. Esto significa que r = x + y + z no implicará copiar vectores. En cambio, los vectores se acaban de mover. Como es típico, el constructor de movimientos de Vector es trivial para definir ...
Sé que Golang admite pasar tradicionalmente por valor y pasar por referencia usando los indicadores de estilo Go.
¿Go admite "mover la semántica" de la forma en que lo hace C ++ 11, como lo describió Stroustrup anteriormente, para evitar la copia inútil de un lado a otro? Si es así, ¿es esto automático o requiere que hagamos algo en nuestro código para hacerlo realidad?
Nota: Se han publicado algunas respuestas, tengo que digerirlas un poco, así que aún no he aceptado ninguna, gracias.
Stroustrup está hablando de C ++, que le permite pasar contenedores, etc. por valor, por lo que la copia excesiva se convierte en un problema.
En Go, (como en Delphi, Java, etc.) cuando pasas un tipo de contenedor, etc., siempre son referencias, por lo que no es un problema. De todos modos, no tiene que lidiar con eso o preocuparse por GoLang: el compilador hace lo que tiene que hacer, y por lo que he visto hasta ahora, lo está haciendo bien.
Tnx a @KerrekSB por ponerme en el camino correcto.
@KerrekSB - Espero que esta sea la respuesta correcta. Si está mal, no tienes ninguna responsabilidad :)
El desglose es como aquí:
- Todo en Go pasa por valor.
- Pero hay tres "tipos de referencia" integrados que también se pasan por valor, pero internamente contienen referencias a la estructura de datos mantenida por separado: mapas, sectores, canales.
Su propia respuesta, @Vector, es incorrecta, es que nada en Go se pasa por referencia. Por el contrario, hay tipos con semántica de referencia. Los valores de ellos todavía se pasan por valor (sic!).
Su confusión se debe supuestamente al hecho de que supuestamente su mente está cargada actualmente por C ++, Java, etc., mientras que estas cosas en Go se realizan principalmente "como en C".
Tome arrays y rebanadas, por ejemplo. Una matriz se pasa por valor en Ir, pero una división es una estructura empaquetada que contiene un puntero (a una matriz subyacente) y dos enteros de tamaño de plataforma (la longitud y la capacidad de la división), y es el valor de esta estructura que se copia - un puntero y dos enteros - cuando se asigna o se devuelve, etc. Si copia una matriz "vacía", se copiaría literalmente - con todos sus elementos.
Lo mismo aplica a canales y mapas. Puede pensar en los tipos que definen canales y mapas como declarados de la siguiente manera:
type Map struct {
impl *mapImplementation
}
type Slice struct {
impl *sliceImplementation
}
(Por cierto, si conoce C++
, debe tener en cuenta que algunos códigos C++
utilizan este truco para reducir la exposición de los detalles en los archivos de encabezado).
Entonces cuando más tarde tienes
m := make(map[int]string)
podrías pensar que m
tiene el tipo Map
y así cuando más tarde
x := m
el valor de m
se copia, pero contiene solo un puntero, por lo que tanto x
como m
ahora hacen referencia a la misma estructura de datos subyacente. ¿Se copió por referencia ("semántica de movimiento")? ¡Seguramente no! ¿Los valores de tipo map y slice y channel tienen semantinc de referencia? ¡Sí!
Tenga en cuenta que estos tres tipos de este tipo no son para nada especiales: la implementación de su tipo personalizado al insertar en él un puntero a alguna estructura de datos complicada es un patrón bastante común.
En otras palabras, Go le permite al programador decidir qué semántica quiere para sus tipos. Y sucede que Go tiene tres tipos incorporados que ya tienen semántica de referencia (mientras que todos los demás tipos incorporados tienen semántica de valores). Escoger una semántica sobre la otra no afecta la regla de copiar todo por valor de ninguna manera. Por ejemplo, está bien tener punteros a valores de cualquier tipo en Go y asignarlos (siempre que tengan tipos compatibles): estos punteros se copiarán por valor.
Otro ángulo para considerar esto es que muchos paquetes Go (estándar y de terceros) prefieren trabajar con punteros a valores (complejos). Un ejemplo es os.Open()
(que abre un archivo en un sistema de archivos) que devuelve un valor del tipo *os.File
. Es decir, devuelve un puntero y espera que el código de llamada pase este puntero. Seguramente, los autores de Go podrían haber declarado que os.File
es una struct
contiene un único puntero, lo que hace que este valor tenga semántica de referencia, pero no lo hicieron. Creo que la razón de esto es que no existe una sintaxis especial para trabajar con los valores de este tipo, por lo que no hay razón para hacer que funcionen como mapas, canales y sectores. KISS, en otras palabras.
Lectura recomendada:
- "Go Data Structures"
- "Go Slices: uso e internos"
- Arrays, slices (y strings): la mecánica de ''append'' "
- Un thead en
golang-nuts
- presta mucha atención a la respuesta de Rob Pike.