¿Es mejor en C++ pasar por valor o pasar por referencia constante?


¿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 debe proporcionar un mejor rendimiento en el programa porque no está haciendo una copia de la variable.

Author: Kevin, 2008-11-07

10 answers

Solía ser una buena práctica generalmente recomendada1 para usar pass by const ref para todos los tipos , excepto para los tipos incorporados(char, int, double, etc.), para iteradores y para objetos de función (lambdas, clases derivadas de std::*_function).

Esto era especialmente cierto antes de la existencia de semántica de movimientos. La razón es simple: si pasó por valor, se tuvo que hacer una copia del objeto y, a excepción de objetos muy pequeños, esto siempre es más caro 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 rvalue.

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á propiedad del argumento.2

En estas situaciones tenemos la siguiente (simplificada) compensación:

  1. Podemos pasar el objeto por referencia, luego copiar internamente.
  2. Podemos pasar el objeto por valor.

"Pasar por valor" todavía hace que el objeto se copie, a menos que el objeto sea un rvalue. En el caso de un rvalue, el objeto se puede mover en su lugar, de modo que el segundo caso de repente ya no es "copiar, luego mover "sino" mover, entonces (potencialmente) mover de nuevo".

Para 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 pass by value si la función toma posesión del argumento, y si el tipo de objeto soporta movimiento eficiente.


Una nota histórica:

De hecho, cualquier compilador moderno debería ser capaz de averiguar cuándo pasar por valor es costoso, e implícitamente convierte la llamada para usar una const ref si es posible.

En teoría. 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á alineada) la copia será realmente elide si el compilador puede averiguar que el objeto original no será cambiado 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.

 175
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:33

Editar: Nuevo artículo de Dave Abrahams en cpp-siguiente:

¿Quieres velocidad? Pase 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 se alias (no son los mismos objetos). Usando pasar-por-referencia el compilador no puede asumir eso siempre. Ejemplo simple:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

El compilador puede optimizarlo en

g.i = 15;
f->i = 2;

Ya que sabe que f y g no comparten la mismo lugar. si g era una referencia (foo&), el compilador no podría haber asumido eso. puesto que g. i podría entonces ser aliased por f- > i y tiene que tener un valor de 7. así que el compilador tendría que volver a recuperar el nuevo valor de g. i de memoria.

Para más reglas prácticas, aquí hay un buen conjunto de reglas que se encuentran en el artículo Move Constructores (lectura muy recomendable).

  • Si la función tiene la intención de cambiar el argumento como un efecto secundario, tómelo por referencia no constante.
  • Si la función no modifica su argumento y el argumento es de tipo primitivo, tómelo por valor.
  • De lo contrario tómelo por referencia constante, excepto en los siguientes casos
    • Si la función necesitaría hacer una copia de la referencia const de todos modos, tómela por valor.

"Primitive" above significa básicamente tipos de datos pequeños que tienen unos pocos bytes de largo y no son polimórficos (iteradores, objetos de función, etc.)...) o caro de copiar. En ese papel, hay otra regla. La idea es que a veces uno quiere hacer una copia (en caso de que el argumento no pueda ser modificado), y a veces uno no quiere (en caso de que uno 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 utilizar de forma nativa con soporte de lenguaje. Hasta entonces, yo iría con las reglas anteriores.

Ejemplos: Para hacer una cadena en mayúsculas y devuelve la versión en mayúsculas, siempre se debe pasar por valor: Uno tiene que tomar una copia de ella de todos modos (uno no podría cambiar la referencia const directamente) - así que mejor que sea lo más transparente posible para la persona que llama y hacer que la copia temprana para que la persona que llama puede optimizar tanto como sea posible - como se detalla en ese documento:

my::string uppercase(my::string s) { /* change s and return it */ }

Sin embargo, si no necesita cambiar el parámetro de todos modos, tómelo por referencia a const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

Sin embargo, si el propósito del parámetro es escribe algo en el argumento, luego pásalo por referencia no constante

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}
 90
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-09 21:32:09

Depende del tipo. Usted está agregando la pequeña sobrecarga de tener que hacer una referencia y desreferencia. Para tipos con un tamaño igual o menor que los punteros que utilizan el copy ctor predeterminado, probablemente sería más rápido pasar por valor.

 12
Author: Lou Franco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-06 21:47:18

Como se ha señalado, depende del tipo. Para los tipos de datos incorporados, es mejor pasar por valor. Incluso algunas estructuras muy pequeñas, como un par de ints pueden funcionar mejor al pasar por valor.

Aquí hay un ejemplo, supongamos 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 sea referencia, primero debe ser almacenado en memoria y luego un puntero a esa memoria colocada en la pila para realizar la llamada. Si se estaba pasando 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 diferentes sistemas de llamadas y CPU).

Si está haciendo programación de plantillas, generalmente se ve obligado a pasar siempre por const ref ya que no conoce los tipos que se pasan. Pasar penalizaciones por pasar algo malo por valor son mucho peores que las penalizaciones de pasar un tipo incorporado por const ref.

 8
Author: Torlack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-08 17:36:17

Parece que tienes tu respuesta. Pasar por valor es caro, pero le da una copia para trabajar con si lo necesita.

 5
Author: GeekyMonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-06 21:44:24

Como regla, pasar por referencia const es mejor. Pero si necesita modificar su argumento de función localmente, debería usar mejor pasar por valor. Para algunos tipos básicos el rendimiento en general es el mismo tanto para pasar por valor como por referencia. En realidad referencia representada internamente por puntero, es por eso que se puede esperar, por ejemplo, que para puntero ambos pasando son los mismos en términos de rendimiento, o incluso pasando por valor puede ser más rápido debido a la desreferencia innecesaria.

 4
Author: sergtk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-06 21:50:21

Esto es con lo que normalmente trabajo al diseñar la interfaz de una función que no es plantilla:

  1. Pasar por valor si la función no desea modificar el parámetro y el el valor es barato de copiar (int, double, float,char, bool, etc... Observe que std:: string, std:: vector y el resto de los contenedores de la biblioteca estándar NO lo son)

  2. Pasar por el puntero const si el valor es caro de copiar y la función lo hace no desea modificar el valor apuntado y NULL es un valor que maneja la función.

  3. Pasar por puntero no-const si el valor es caro de copiar y la función quiere modificar el valor apuntado y NULL es un valor que maneja la función.

  4. Pasar por referencia const cuando el valor es caro de copiar y la función no desea modificar el valor al que se hace referencia y NULL no sería un valor válido si se utiliza un puntero en su lugar.

  5. Pasar por referencia no constante cuando el el valor es caro de copiar y la función quiere modificar el valor al que se hace referencia y NULL no sería un valor válido si se utiliza un puntero en su lugar.

 4
Author: Martin G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 07:02:41

Como regla general, valor para tipos que no son de clase y referencia const para clases. Si una clase es realmente pequeña es probablemente mejor pasar por valor, pero la diferencia es mínima. Lo que realmente quieres evitar es pasar una clase gigantesca por valor y tener todo duplicado - esto hará una gran diferencia si estás pasando, digamos, un vector std::con bastantes elementos en él.

 1
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-06 22:04:00

Pasar por valor para tipos pequeños.

Pase por referencias const 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:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Ahora el código de llamada haría:

Person p(std::string("Albert"));

Y solo se crearía un objeto y se movería directamente al miembro name_ en la clase Person. Si pasa por referencia const, se tendrá que hacer una copia para ponerlo en name_.

 1
Author: Germán Diago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-29 13:48:09

Diferencia simple :- En la función tenemos parámetro de entrada y salida , por lo que si su entrada de paso y el parámetro de salida es el mismo entonces utilizar la llamada por referencia de lo contrario si la entrada y el parámetro de salida son diferentes entonces mejor utilizar la llamada por valor .

Ejemplo void amount(int account , int deposit , int total )

Parámetro de entrada: cuenta, depósito parámetro de salida: total

La entrada y la salida son llamadas de uso diferentes por vaule

  1. void amount(int total , int deposit )

Entrada depósito total producción total

 -5
Author: Dhirendra Sengar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-05 09:37:55