En C ++ tradicional, pasar por valor a funciones y métodos es lento para objetos grandes, y generalmente está mal visto. En cambio, los programadores de C ++ tienden a pasar referencias, lo que es más rápido, pero que introduce todo tipo de preguntas complicadas sobre la propiedad y especialmente sobre la administración de memoria (en el caso de que el objeto esté asignado en un montón)
Ahora, en C ++ 11, tenemos referencias de Rvalue y constructores de movimiento, lo que significa que es posible implementar un objeto grande (como un std::vector
) que es barato pasar por valor dentro y fuera de una función.
Entonces, ¿esto significa que el valor predeterminado debería ser pasar por valor para instancias de tipos como std::vector
y std::string
? ¿Qué pasa con los objetos personalizados? ¿Cuál es la nueva mejor práctica?
fuente
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)
. No entiendo cómo es complicado o problemático para la propiedad. ¿Se me puede pasar algo por alto?const std::string&
y no una copia. El primer hilo salió ...Respuestas:
Es un valor predeterminado razonable si necesita hacer una copia dentro del cuerpo. Esto es lo que Dave Abrahams defiende :
En código esto significa que no hagas esto:
pero haz esto:
que tiene la ventaja de que la persona que llama puede usar
foo
así:y solo se realiza un trabajo mínimo. Necesitaría dos sobrecargas para hacer lo mismo con las referencias,
void foo(T const&);
yvoid foo(T&&);
.Con eso en mente, ahora escribí mis valiosos constructores como tales:
De lo contrario, pasar por referencia a
const
todavía es razonable.fuente
SomeProperty p;
for (auto x: vec) { x.foo(p); }
por ejemplo, no cabe. Además, los constructores de movimiento tienen un costo (cuanto mayor es el objeto, mayor es el costo) mientras queconst&
son esencialmente gratuitos.std::vector
con un millón de elementos cuesta lo mismo que mover uno con cinco elementos, ya que solo se mueve el puntero a la matriz en el montón, no todos los objetos en el vector. Entonces, en realidad no es un problema tan grande.std::move
todo el lugar ..const&
eso, que me ha hecho tropezar varias veces.void foo(const T&); int main() { S s; foo(s); }
. Esto puede compilarse, aunque los tipos son diferentes, si hay un constructor T que toma una S como argumento. Esto puede ser lento, porque se puede construir un objeto T grande. Puede pensar que está pasando una referencia sin copiar, pero puede ser. Vea esta respuesta a una pregunta que hice más. Básicamente,&
generalmente se une solo a los valores, pero hay una excepción pararvalue
. Hay alternativasEn casi todos los casos, su semántica debe ser:
Todas las demás firmas deben usarse con moderación y con buena justificación. El compilador ahora casi siempre los resolverá de la manera más eficiente. ¡Puedes seguir escribiendo tu código!
fuente
foo(bar& x) { x.a = 3; }
Es muchísimo más confiable (¡y legible!) Quefoo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
ref
palabra clave en C #).is shared_ptr intended to never be null? Much as (I think) unique_ptr is?
Ambos supuestos son incorrectos.unique_ptr
yshared_ptr
puede contener nulo /nullptr
valores. Si no desea preocuparse por los valores nulos, debe usar referencias, porque nunca pueden ser nulas. Tampoco tendrá que escribir->
, lo que le resultará molesto :)Pase los parámetros por valor si dentro del cuerpo de la función necesita una copia del objeto o solo necesita mover el objeto. Pase
const&
si solo necesita acceso no mutante al objeto.Ejemplo de copia de objeto:
Ejemplo de movimiento de objeto:
Ejemplo de acceso no mutante:
Para una explicación, vea estas publicaciones de blog de Dave Abrahams y Xiang Fan .
fuente
La firma de una función debe reflejar su uso previsto. La legibilidad es importante, también para el optimizador.
Esta es la mejor condición previa para que un optimizador cree un código más rápido, al menos en teoría y, si no en la realidad, en unos pocos años.
Las consideraciones de rendimiento a menudo se sobrevaloran en el contexto del paso de parámetros. El reenvío perfecto es un ejemplo. Las funciones como
emplace_back
son en su mayoría muy cortas e integradas de todos modos.fuente