Cuando una función toma un shared_ptr
(de boost o C ++ 11 STL), lo está pasando:
por referencia constante:
void foo(const shared_ptr<T>& p)
o por valor
void foo(shared_ptr<T> p)
:?
Preferiría el primer método porque sospecho que sería más rápido. ¿Pero realmente vale la pena o hay problemas adicionales?
¿Podría dar los motivos de su elección o, en su caso, por qué cree que no importa?
c++
c++11
boost
shared-ptr
Danvil
fuente
fuente
shared_ptr
, y puedo cambiarlo si quiero", mientras que la versión de valor dice "Voy a copiar tushared_ptr
, así que mientras pueda cambiarlo, nunca lo sabrás". ) Un parámetro de referencia constante es la solución real, que dice "Voy a alias algunosshared_ptr
, y prometo no cambiarlo" (¡Lo cual es extremadamente similar a la semántica por valor!)shared_ptr
miembro de la clase. ¿Lo haces por const-refs?Respuestas:
Scott, Andrei y Herb han discutido y respondido esta pregunta durante la sesión Ask Us Anything en C ++ y más allá de 2011 . Mire a partir de las 4:34 sobre
shared_ptr
rendimiento y corrección .En breve, no hay razón para pasar por valor, a menos que el objetivo sea compartir la propiedad de un objeto (por ejemplo, entre diferentes estructuras de datos o entre diferentes hilos).
A menos que pueda moverlo, optimizarlo como lo explica Scott Meyers en el video de conversación vinculado anteriormente, pero eso está relacionado con la versión real de C ++ que puede usar.
Se produjo una actualización importante de esta discusión durante el panel interactivo de la conferencia GoingNative 2012 : ¡Pregúntenos cualquier cosa! que vale la pena ver, especialmente a partir de las 22:50 .
fuente
Value*
es corto y legible, pero es malo, así que ahora mi código está llenoconst shared_ptr<Value>&
y es significativamente menos legible y solo ... menos ordenado. Lo que solía servoid Function(Value* v1, Value* v2, Value* v3)
ahoravoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, ¿y la gente está de acuerdo con esto?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
entonces su función se vuelve más simple:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)
y obtiene el máximo rendimiento. Por eso usas C ++, ¿no? :)Aquí está la toma de Herb Sutter
fuente
Personalmente, usaría una
const
referencia. No es necesario incrementar el recuento de referencia solo para disminuirlo nuevamente en aras de una llamada de función.fuente
shared_ptr
funciona, el único inconveniente posible de no pasar por referencia es una ligera pérdida de rendimiento. Hay dos causas aquí. a) la característica de alias de puntero significa que se copian datos de punteros más un contador (quizás 2 para referencias débiles), por lo que es un poco más costoso copiar la ronda de datos. b) el recuento de referencia atómica es ligeramente más lento que el antiguo código de incremento / decremento simple, pero es necesario para ser seguro para subprocesos. Más allá de eso, los dos métodos son los mismos para la mayoría de los intentos y propósitos.Pase por
const
referencia, es más rápido. Si necesita almacenarlo, digamos en algún contenedor, la ref. El conteo se incrementará automáticamente por la operación de copia.fuente
Ejecuté el siguiente código, una vez
foo
tomando elshared_ptr
byconst&
y otra vezfoo
tomando elshared_ptr
by.Usando VS2015, compilación de lanzamiento x86, en mi procesador Intel Core 2 quad (2.4GHz)
La versión de copia por valor fue un orden de magnitud más lenta.
Si está llamando a una función sincrónicamente desde el hilo actual, prefiera la
const&
versión.fuente
foo()
función ni siquiera debería aceptar un puntero compartido en primer lugar porque no está usando este objeto: debería aceptar ayint&
hacerp = ++x;
, llamandofoo(*p);
desdemain()
. Una función acepta un objeto puntero inteligente cuando necesita hacer algo con él, y la mayoría de las veces, lo que necesita hacer es moverlo (std::move()
) a otro lugar, por lo que un parámetro de valor no tiene costo.Desde C ++ 11 deberías tomarlo por valor sobre const y con más frecuencia de lo que piensas.
Si está tomando std :: shared_ptr (en lugar del tipo T subyacente), lo está haciendo porque desea hacer algo con él.
Si desea copiarlo en algún lugar, tiene más sentido tomarlo por copia, y std :: moverlo internamente, en lugar de tomarlo por constante y luego copiarlo. Esto se debe a que le permite a la persona que llama la opción a su vez std :: mover el shared_ptr al llamar a su función, ahorrándose así un conjunto de operaciones de incremento y decremento. O no. Es decir, el llamador de la función puede decidir si necesita o no std :: shared_ptr después de llamar a la función, y dependiendo de si se mueve o no. Esto no se puede lograr si pasa por const &, por lo que es preferible tomarlo por valor.
Por supuesto, si la persona que llama necesita su shared_ptr durante más tiempo (por lo tanto, no puede std :: moverlo) y no desea crear una copia simple en la función (digamos que quiere un puntero débil, o solo a veces quiere copiarlo, dependiendo de alguna condición), entonces una constante y aún podría ser preferible.
Por ejemplo, deberías hacer
encima
Porque en este caso siempre creas una copia internamente
fuente
Sin saber el costo de tiempo de la operación de copia shared_copy donde está el incremento y decremento atómico, sufrí un problema de uso de CPU mucho mayor. Nunca esperé que el incremento y la disminución atómicos puedan tomar tanto costo.
Siguiendo el resultado de mi prueba, el incremento y decremento atómico int32 tarda 2 o 40 veces más que el incremento y decremento no atómico. Lo obtuve en 3GHz Core i7 con Windows 8.1. El primer resultado sale cuando no ocurre contención, el segundo cuando ocurre una alta posibilidad de contienda. Tengo en cuenta que las operaciones atómicas son, por fin, un bloqueo basado en hardware. Cerradura es cerradura. Malo para el rendimiento cuando se produce la contención.
Experimentando esto, siempre uso byref (const shared_ptr &) que byval (shared_ptr).
fuente
Hubo una publicación reciente en el blog: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
Entonces la respuesta a esto es: (casi) nunca pases de largo
const shared_ptr<T>&
.Simplemente pasa la clase subyacente en su lugar.
Básicamente, los únicos tipos de parámetros razonables son:
shared_ptr<T>
- Modificar y tomar posesiónshared_ptr<const T>
- No modificar, tomar posesiónT&
- Modificar, sin propiedadconst T&
- No modificar, sin propiedadT
- No modificar, sin propiedad, barato para copiarComo señaló @accel en https://stackoverflow.com/a/26197326/1930508, el consejo de Herb Sutter es:
¿Pero en cuántos casos no estás seguro? Entonces esta es una situación rara
fuente
Es conocido el problema de que pasar shared_ptr por valor tiene un costo y debe evitarse si es posible.
El costo de pasar por shared_ptr
La mayoría de las veces pasaría shared_ptr por referencia, e incluso mejor por referencia constante.
La directriz central de cpp tiene una regla específica para pasar shared_ptr
R.34: tome un parámetro shared_ptr para expresar que una función es propietaria de una parte
Un ejemplo de cuándo es realmente necesario pasar shared_ptr por valor es cuando la persona que llama pasa un objeto compartido a una persona que llama asincrónica, es decir, la persona que llama queda fuera de alcance antes de que la persona que llama complete su trabajo. La persona que llama debe "extender" la vida útil del objeto compartido tomando un share_ptr por valor. En este caso, pasar una referencia a shared_ptr no funcionará.
Lo mismo ocurre para pasar un objeto compartido a un hilo de trabajo.
fuente
shared_ptr no es lo suficientemente grande, ni su constructor \ destructor hace suficiente trabajo para que haya suficiente sobrecarga de la copia para preocuparse por el rendimiento de paso por referencia vs paso por copia.
fuente
shared_ptr<int>
valor por toma más de 100 instrucciones x86 (incluidas laslock
instrucciones de edición costosas para aumentar / disminuir atómicamente el recuento de referencia). Pasar por referencia constante es lo mismo que pasar un puntero a cualquier cosa (y en este ejemplo en el explorador del compilador Godbolt, la optimización de llamada de cola convierte esto en un simple jmp en lugar de una llamada: godbolt.org/g/TazMBU ).