¿Es mejor en C ++ pasar por valor o pasar por referencia constante?
Me pregunto cuál es la mejor práctica. Me doy cuenta de que pasar por referencia constante debería proporcionar un mejor rendimiento en el programa porque no está haciendo una copia de la variable.
c++
variables
pass-by-reference
constants
pass-by-value
Matt Pascoe
fuente
fuente
Respuestas:
Antes se recomienda generalmente las mejores prácticas 1 a pase utilización por ref const de todo tipo , a excepción de orden interna (tipos
char
,int
,double
, etc.), para iteradores y para los objetos de función (lambdas, clases derivadas destd::*_function
).Esto era especialmente cierto antes de la existencia de la semántica de movimiento . La razón es simple: si pasa por valor, debe hacerse una copia del objeto y, a excepción de los objetos muy pequeños, esto siempre es más costoso que pasar una referencia.
Con C ++ 11, hemos ganado semántica de movimiento . En pocas palabras, la semántica de movimiento permite que, en algunos casos, un objeto se pueda pasar "por valor" sin copiarlo. En particular, este es el caso cuando el objeto que está pasando es un valor r .
En sí mismo, mover un objeto sigue siendo al menos tan costoso como pasar por referencia. Sin embargo, en muchos casos, una función copiará internamente un objeto de todos modos, es decir, tomará posesión del argumento. 2
En estas situaciones tenemos la siguiente compensación (simplificada):
"Pasar por valor" todavía hace que el objeto se copie, a menos que el objeto sea un valor. En el caso de un valor r, el objeto se puede mover en su lugar, de modo que el segundo caso ya no sea "copiar, luego mover" sino "mover, luego (potencialmente) mover de nuevo".
Para los objetos grandes que implementan constructores de movimiento adecuados (como vectores, cadenas ...), el segundo caso es mucho más eficiente que el primero. Por lo tanto, se recomienda usar pasar por valor si la función toma posesión del argumento y si el tipo de objeto admite un movimiento eficiente .
Una nota histórica:
De hecho, cualquier compilador moderno debería poder darse cuenta cuando pasar por valor es costoso, y convertir implícitamente la llamada para usar una constante const si es posible.
En teoria. En la práctica, los compiladores no siempre pueden cambiar esto sin romper la interfaz binaria de la función. En algunos casos especiales (cuando la función está en línea), la copia se eliminará si el compilador puede descubrir que el objeto original no se cambiará a través de las acciones en la función.
Pero, en general, el compilador no puede determinar esto, y el advenimiento de la semántica de movimiento en C ++ ha hecho que esta optimización sea mucho menos relevante.
1 Por ejemplo, en Scott Meyers, C ++ efectivo .
2 Esto es especialmente cierto para los constructores de objetos, que pueden tomar argumentos y almacenarlos internamente para formar parte del estado del objeto construido.
fuente
Editar: Nuevo artículo de Dave Abrahams en cpp-next:
¿Quieres velocidad? Pasar por valor.
Pasar por valor para estructuras donde la copia es barata tiene la ventaja adicional de que el compilador puede asumir que los objetos no tienen alias (no son los mismos objetos). Usando paso por referencia, el compilador no puede asumir eso siempre. Ejemplo simple:
el compilador puede optimizarlo en
ya que sabe que f y g no comparten la misma ubicación. Si g fuera una referencia (foo &), el compilador no podría haber asumido eso. dado que gi podría ser alias por f-> i y tener que tener un valor de 7. por lo que el compilador tendría que recuperar el nuevo valor de gi de la memoria.
Para reglas más prácticas, aquí hay un buen conjunto de reglas que se encuentran en el artículo Move Constructors (lectura muy recomendable).
"Primitivo" arriba significa básicamente tipos de datos pequeños que tienen unos pocos bytes de longitud y no son polimórficos (iteradores, objetos de función, etc.) ni caros de copiar. En ese documento, hay otra regla. La idea es que a veces uno quiere hacer una copia (en caso de que el argumento no se pueda modificar), y a veces no quiere (en caso de que quiera usar el argumento en sí mismo en la función si el argumento era temporal de todos modos , por ejemplo). El documento explica en detalle cómo se puede hacer eso. En C ++ 1x, esa técnica se puede usar de forma nativa con soporte de lenguaje. Hasta entonces, iría con las reglas anteriores.
Ejemplos: para poner una cadena en mayúscula y devolver la versión en mayúscula, siempre se debe pasar por valor: de todos modos, se debe tomar una copia (no se puede cambiar la referencia constante directamente), así que mejor hacerlo lo más transparente posible la persona que llama y haga esa copia temprano para que la persona que llama pueda optimizar tanto como sea posible, como se detalla en ese documento:
Sin embargo, si no necesita cambiar el parámetro de todos modos, tómelo como referencia para const:
Sin embargo, si el propósito del parámetro es escribir algo en el argumento, entonces páselo por referencia no constante
fuente
__restrict__
(que también puede funcionar en referencias) que hacer copias excesivas. Lástima que C ++ no haya adoptado larestrict
palabra clave de C99 .Depende del tipo. Está agregando la pequeña sobrecarga de tener que hacer una referencia y desreferenciar. Para los tipos con un tamaño igual o menor que los punteros que utilizan el copiador predeterminado, probablemente sería más rápido pasar por valor.
fuente
Como se ha señalado, depende del tipo. Para los tipos de datos integrados, es mejor pasar por valor. Incluso algunas estructuras muy pequeñas, como un par de entradas, pueden funcionar mejor al pasar por valor.
Aquí hay un ejemplo, suponga que tiene un valor entero y desea pasarlo a otra rutina. Si ese valor ha sido optimizado para ser almacenado en un registro, entonces si desea pasarlo como referencia, primero debe almacenarse en la memoria y luego un puntero a esa memoria colocado en la pila para realizar la llamada. Si se pasaba por valor, todo lo que se requiere es el registro empujado a la pila. (Los detalles son un poco más complicados que los dados con diferentes sistemas de llamadas y CPU).
Si está haciendo programación de plantillas, por lo general se ve obligado a pasar siempre por const ref ya que no conoce los tipos que se están pasando. Pasar las penalizaciones por pasar algo malo por valor es mucho peor que las penalizaciones de pasar un tipo incorporado por const ref.
fuente
Esto es lo que normalmente trabajo cuando diseño la interfaz de una función que no es de plantilla:
Pase por valor si la función no quiere modificar el parámetro y el valor es barato de copiar (int, double, float, char, bool, etc ... Observe que std :: string, std :: vector y el resto de los contenedores en la biblioteca estándar NO son)
Pase por el puntero constante si el valor es costoso de copiar y la función no desea modificar el valor señalado y NULL es un valor que maneja la función.
Pase por un puntero no constante si el valor es costoso de copiar y la función desea modificar el valor señalado y NULL es un valor que maneja la función.
Pase por referencia constante cuando el valor es costoso de copiar y la función no quiere modificar el valor mencionado y NULL no sería un valor válido si se utilizara un puntero.
Pase por referencia no constante cuando el valor es costoso de copiar y la función quiere modificar el valor mencionado y NULL no sería un valor válido si se utilizara un puntero.
fuente
std::optional
a la imagen y ya no necesita punteros.Parece que tienes tu respuesta. Pasar por valor es costoso, pero le da una copia para trabajar si lo necesita.
fuente
Como regla, pasar por referencia constante es mejor. Pero si necesita modificar el argumento de su función localmente, debería usar el paso por valor. Para algunos tipos básicos, el rendimiento en general es el mismo tanto para pasar por valor como por referencia. En realidad, la referencia interna representada por el puntero, es por eso que puede esperar, por ejemplo, que para el puntero ambos pases sean iguales en términos de rendimiento, o incluso pasar por valor puede ser más rápido debido a una desreferencia innecesaria.
fuente
Como regla general, valor para tipos que no son de clase y referencia constante para clases. Si una clase es realmente pequeña, probablemente sea mejor pasar por valor, pero la diferencia es mínima. Lo que realmente desea evitar es pasar una clase gigantesca por valor y tenerlo todo duplicado; esto hará una gran diferencia si está pasando, por ejemplo, un std :: vector con bastantes elementos en él.
fuente
std::vector
realidad asigna sus elementos en el montón y el objeto vector en sí nunca crece. Oh espera. Sin embargo, si la operación hace que se haga una copia del vector, de hecho irá y duplicará todos los elementos. Eso seria malo.sizeof(std::vector<int>)
es constante, pero pasarlo por valor seguirá copiando el contenido en ausencia de inteligencia del compilador.Pase por valor para tipos pequeños.
Pase por referencias constantes para tipos grandes (la definición de grande puede variar entre máquinas) PERO, en C ++ 11, pase por valor si va a consumir los datos, ya que puede explotar la semántica de movimiento. Por ejemplo:
Ahora el código de llamada haría:
Y solo se crearía un objeto y se movería directamente al miembro
name_
en clasePerson
. Si pasa por referencia constante, tendrá que hacer una copia para ponerlo enname_
.fuente
Diferencia simple: - En la función tenemos parámetros de entrada y salida, por lo que si su parámetro de entrada y salida de paso es el mismo, use la llamada por referencia; de lo contrario, si el parámetro de entrada y salida es diferente, es mejor usar la llamada por valor.
ejemplo
void amount(int account , int deposit , int total )
parámetro de entrada: cuenta, parámetro de salida de depósito: total
entrada y salida es llamada de uso diferente por vaule
void amount(int total , int deposit )
entrada total depósito salida total
fuente