“Const T & arg” vs. “T arg”

¿Cuál de los siguientes ejemplos es la mejor manera de declarar la siguiente función y por qué?

void myFunction (const int &myArgument); 

o

 void myFunction (int myArgument); 

Utilice const T & arg si sizeof(T)>sizeof(void*) y use T arg si sizeof(T) < = sizeof(void*)

Ellos hacen cosas diferentes. const T& hace que la función tome una referencia a la variable. Por otro lado, T arg llamará al constructor de copia del objeto y pasará la copia . Si no se puede acceder al constructor de copia (por ejemplo, es private ), T arg no funcionará:

 class Demo { public: Demo() {} private: Demo(const Demo& t) { } }; void foo(Demo t) { } int main() { Demo t; foo(t); // error: cannot copy `t`. return 0; } 

Para valores pequeños como tipos primitivos (donde todas las cuestiones son los contenidos del objeto, no la identidad referencial real; por ejemplo, no es un identificador o algo así), generalmente se prefiere T arg . Para objetos grandes y objetos que no puede copiar y / o preservar la identidad referencial es importante (independientemente del tamaño), se prefiere pasar la referencia.

Otra ventaja de T arg es que, dado que es una copia, el destinatario no puede alterar el valor original de manera maliciosa. Puede mutar libremente la variable como cualquier variable local para hacer su trabajo.

Tomado de mover constructores . Me gustan las reglas fáciles

  1. Si la función tiene la intención de cambiar el argumento como un efecto secundario, tómelo por referencia / puntero a un objeto no const. Ejemplo:

     void Transmogrify(Widget& toChange); void Increment(int* pToBump); 
  2. Si la función no modifica su argumento y el argumento es de tipo primitivo, tómalo por valor. Ejemplo:

     double Cube(double value); 
  3. De otra manera

    3.1. Si la función siempre hace una copia de su argumento adentro, tómalo por valor.

    3.2. Si la función nunca hace una copia de su argumento, tómalo por referencia a const.

    3.3. Agregado por mí : si la función a veces hace una copia, entonces decide sobre la sensación visceral: si la copia se hace casi siempre, entonces toma por valor. Si la copia está hecha la mitad del tiempo, vaya a la manera segura y tome por referencia a const.

En su caso, debe tomar el int por valor, porque no tiene la intención de modificar el argumento, y el argumento es de tipo primitivo. Pienso en “tipo primitivo” como un tipo no de clase o un tipo sin un constructor de copia definido por el usuario y donde sizeof(T) es solo un par de bytes.

Existe un consejo popular que establece que el método de aprobación (“por valor” frente a “por referencia constante”) debe elegirse dependiendo del tamaño real del tipo que se va a aprobar. Incluso en esta discusión tiene una respuesta etiquetada como “correcta” que sugiere exactamente eso.

En realidad, basar su decisión en el tamaño del tipo no solo es incorrecto, este es un error de diseño importante y más bien evidente, que revela una grave falta de intuición / comprensión de buenas prácticas de progtwigción.

Las decisiones basadas en los tamaños físicos reales de los objetos que dependen de la implementación deben dejarse al comstackdor tan a menudo como sea posible. Tratar de “adaptar” su código a estos tamaños codificando el método de paso es una pérdida de esfuerzo completamente contraproducente en 99 de cada 100 casos. (Sí, es cierto, que en el caso del lenguaje C ++, el comstackdor no tener suficiente libertad para usar estos métodos indistintamente – en general, no son intercambiables en C ++. Aunque, si es necesario, se puede implementar una selección adecuada de metarios semiautomáticos basados ​​en el tamaño a través de la metaprogtwigción de plantillas, pero esa es una historia diferente )

El criterio mucho más significativo para seleccionar el método de aprobación cuando escribe el código “a mano” puede sonar de la siguiente manera:

  1. Prefiere pasar “por valor” cuando está pasando una entidad atómica, unitaria e indivisible, como un único valor no agregado de cualquier tipo: un número, un puntero, un iterador. Tenga en cuenta que, por ejemplo, los iteradores son valores unitarios en el nivel lógico. Por lo tanto, prefieren pasar iteradores por valor , independientemente de si su tamaño real es mayor que sizeof (void *). (La implementación de STL hace exactamente eso, por cierto).

  2. Prefiere pasar “por referencia constante” cuando está pasando un valor compuesto agregado de cualquier tipo. es decir, un valor que ha expuesto la naturaleza pronunciadamente “compuesta” en el nivel lógico, incluso si su tamaño no es mayor que sizeof (void *).

La separación entre los dos no siempre es clara, pero cómo siempre están las cosas con todas esas recomendaciones. Además, la separación en entidades “atómicas” y “compuestas” podría depender de las características específicas de su diseño, por lo que la decisión en realidad podría diferir de un diseño a otro.

Tenga en cuenta que esta regla puede producir decisiones diferentes a las del método basado en el tamaño supuestamente “correcto” mencionado en esta discusión.

Como ejemplo, es interesante observar que el método basado en el tamaño sugerirá que codifique manualmente diferentes métodos de pase para diferentes tipos de iteradores, dependiendo de su tamaño físico. Esto hace que sea especialmente obvio cuán falso es el método basado en el tamaño.

Una vez más, uno de los principios básicos del cual se derivan las buenas prácticas de progtwigción es evitar basar sus decisiones en las características físicas de la plataforma (tanto como sea posible). En cambio, las decisiones deben basarse en las propiedades lógicas y conceptuales de las entidades en su progtwig (tanto como sea posible). El problema de pasar “por valor” o “por referencia” no es una excepción aquí.


En C ++ 11, la introducción de la semántica de movimientos en el lenguaje produjo un cambio notable en las prioridades relativas de los diferentes métodos de paso de parámetros. Bajo ciertas circunstancias, podría ser perfectamente factible pasar incluso objetos complejos por valor

¿Deberían escribirse todas las funciones / más setter en C ++ 11 como plantillas de funciones que acepten referencias universales?

Contrariamente a las creencias populares y de larga data, pasar de referencia constante no es necesariamente más rápido, incluso cuando pasas por un objeto grande. Es posible que desee leer el reciente artículo de Dave Abrahams sobre este mismo tema.

Editar: (sobre todo en respuesta a los comentarios de Jeff Hardy): es cierto que pasar por referencia constante es probablemente la alternativa “más segura” bajo la mayor cantidad de circunstancias, pero eso no significa que siempre sea lo mejor que se puede hacer. Pero, para entender lo que se discute aquí, realmente necesitas leer el artículo completo de Dave con bastante cuidado, ya que es bastante técnico, y el razonamiento detrás de sus conclusiones no siempre es intuitivamente obvio (y necesitas entender el razonamiento para tomar decisiones inteligentes )

Por lo general, para los tipos integrados solo puede pasar por valor. Son pequeños tipos.

Para los tipos definidos por el usuario (o plantillas, cuando no lo que se va a pasar), prefiera const &. El tamaño de una referencia es probablemente menor que el tamaño del tipo. Y no incurrirá en una copia adicional (no se llama a un constructor de copias).

Bueno, sí … las otras respuestas sobre la eficiencia son ciertas. Pero aquí está sucediendo algo más, lo que es importante: pasar una clase por valor crea una copia y, por lo tanto, invoca el constructor de copia. Si estás haciendo cosas de lujo allí, es otra razón para usar referencias.

Una referencia a const T no vale la pena el esfuerzo de tipeo en el caso de tipos escalares como int, double, etc. La regla de oro es que los tipos de clases deben aceptarse a través de ref-to-const. Pero para los iteradores (que podrían ser tipos de clase) a menudo hacemos una excepción.

En el código genérico, probablemente debería escribir “T const &” la mayor parte del tiempo para estar seguro. También existen rasgos de llamada de boost que puede usar para seleccionar el tipo de paso de parámetro más prometedor. Básicamente, utiliza ref-to-const para tipos de clase y pass-by-value para tipos escalares, hasta donde yo sé.

Pero también hay situaciones en las que es posible que desee aceptar parámetros por valor, independientemente de lo caro que pueda ser crear una copia. Vea el artículo de Dave “¿Quiere velocidad? ¡Use pasar por valor!” .

Para tipos simples como int, double y char *, tiene sentido pasarlo por valor. Para tipos más complejos, uso const T & a menos que haya una razón específica para no hacerlo.

El costo de pasar un parámetro de 4 a 8 bytes es tan bajo como puede obtener. No compras nada pasando una referencia. Para tipos más grandes, pasarlos por valor puede ser costoso.

No hará ninguna diferencia para un int, ya que cuando utiliza una referencia, la dirección de memoria aún tiene que pasar, y la dirección de memoria (void *) suele ser del tamaño de un entero.

Para los tipos que contienen una gran cantidad de datos, se vuelve mucho más eficiente, ya que evita la gran sobrecarga de tener que copiar los datos.

Bueno, la diferencia entre los dos realmente no significa mucho para los ints.

Sin embargo, cuando se usan estructuras (u objetos) más grandes, el primer método que se usa, pasar por referencia constante, le da acceso a la estructura sin necesidad de copiarla. El segundo caso pase por valor instanciará una nueva estructura que tendrá el mismo valor que el argumento.

En ambos casos, ves esto en la persona que llama

 myFunct(item); 

Para la persona que llama, myFunct no cambiará el elemento, pero el pase por referencia no implicará el costo de crear una copia.

Hay una muy buena respuesta a una pregunta similar en Pase por referencia / valor en C ++

La diferencia entre ellos es que uno pasa un int (que se copia), y uno usa el int existente. Como es una referencia const , no cambia, por lo que funciona de manera muy similar. La gran diferencia aquí es que la función puede alterar el valor del int localmente, pero no la referencia const . (Supongo que algún idiota podría hacer lo mismo con const_cast<> , o al menos intentarlo). Para objetos más grandes, puedo pensar en dos diferencias.

Primero, algunos objetos simplemente no se pueden copiar, auto_ptr<> sy los objetos que los contienen son el ejemplo obvio.

En segundo lugar, para objetos grandes y complicados, es más rápido pasar por referencia const que copiar. Por lo general, no es un gran problema, pero pasar objetos por referencias es un hábito útil para entrar.

Cualquiera de los dos funciona bien No pierdas tu tiempo preocupándote por esto.

El único momento en que podría marcar una diferencia es cuando el tipo es una estructura grande, lo que puede ser costoso pasar a la stack. En ese caso, pasar el arg como un puntero o una referencia es (un poco) más eficiente.

El problema aparece cuando pasas objetos. Si pasa por valor, se llamará al constructor de copia. Si no ha implementado uno, se pasará una copia superficial de ese objeto a la función.

¿Por qué es esto un problema? Si tiene punteros a memoria asignada dinámicamente, esto podría liberarse cuando se llame al destructor de la copia (cuando el objeto abandona el scope de la función). Luego, cuando llames a tu destructor, tendrás un doble gratis.

Moraleja: escriba sus constructores de copia.