C ++ - pasando referencias a std :: shared_ptr o boost :: shared_ptr

115

Si tengo una función que necesita trabajar con a shared_ptr, ¿no sería más eficiente pasarle una referencia (para evitar copiar el shared_ptrobjeto)? ¿Cuáles son los posibles efectos secundarios negativos? Visualizo dos casos posibles:

1) dentro de la función se hace una copia del argumento, como en

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) dentro de la función, el argumento solo se usa, como en

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

No veo en ambos casos una buena razón para pasar el boost::shared_ptr<foo>valor por referencia en lugar de por referencia. Pasar por valor solo aumentaría "temporalmente" el recuento de referencias debido a la copia, y luego lo disminuiría al salir del alcance de la función. ¿Estoy pasando por alto algo?

Solo para aclarar, después de leer varias respuestas: estoy perfectamente de acuerdo con las preocupaciones de optimización prematura, y siempre trato de primero perfilar y luego trabajar en los puntos de acceso. Mi pregunta era más desde un punto de vista de código puramente técnico, si sabes a qué me refiero.

abigagli
fuente
No sé si puede modificar las etiquetas de su pregunta, pero intente agregar una etiqueta de impulso allí. Intenté buscar esta pregunta, pero no pude encontrar ninguna porque busqué etiquetas boost y smart-pointer. Así que encontré tu pregunta justo después de redactar mi propia pregunta
Edison Gustavo Muenz

Respuestas:

113

El objetivo de una shared_ptrinstancia distinta es garantizar (en la medida de lo posible) que mientras shared_ptresté dentro del alcance, el objeto al que apunta seguirá existiendo, porque su recuento de referencias será al menos 1.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

Entonces, al usar una referencia a a shared_ptr, deshabilita esa garantía. Entonces, en tu segundo caso:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

¿Cómo sabe que sp->do_something()no explotará debido a un puntero nulo?

Todo depende de lo que haya en esas secciones '...' del código. ¿Qué pasa si llama a algo durante el primer '...' que tiene el efecto secundario (en algún lugar de otra parte del código) de borrar un shared_ptra ese mismo objeto? ¿Y si resulta ser el único que queda distinto shared_ptra ese objeto? Adiós objeto, justo donde estás a punto de intentar usarlo.

Entonces, hay dos formas de responder a esa pregunta:

  1. Examine la fuente de todo su programa con mucho cuidado hasta que esté seguro de que el objeto no morirá durante el cuerpo de la función.

  2. Vuelva a cambiar el parámetro para que sea un objeto distinto en lugar de una referencia.

Un consejo general que se aplica aquí: no se moleste en realizar cambios arriesgados en su código por el bien del rendimiento hasta que haya cronometrado su producto en una situación realista en un generador de perfiles y haya medido de manera concluyente que el cambio que desea realizar hará un Diferencia significativa en el rendimiento.

Actualización para el comentarista JQ

Aquí hay un ejemplo artificial. Es deliberadamente simple, por lo que el error será obvio. En ejemplos reales, el error no es tan obvio porque está oculto en capas de detalles reales.

Tenemos una función que enviará un mensaje a alguna parte. Puede ser un mensaje grande, así que en lugar de usar un mensaje std::stringque probablemente se copie a medida que se transmite a varios lugares, usamos shared_ptruna cadena de caracteres:

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(Simplemente lo "enviamos" a la consola para este ejemplo).

Ahora queremos agregar una función para recordar el mensaje anterior. Queremos el siguiente comportamiento: debe existir una variable que contenga el mensaje enviado más recientemente, pero mientras se está enviando un mensaje, no debe haber ningún mensaje anterior (la variable debe restablecerse antes de enviar). Entonces declaramos la nueva variable:

std::shared_ptr<std::string> previous_message;

Luego modificamos nuestra función de acuerdo con las reglas que especificamos:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

Entonces, antes de comenzar a enviar, descartamos el mensaje anterior actual, y luego, una vez que se completa el envío, podemos almacenar el nuevo mensaje anterior. Todo bien. Aquí hay un código de prueba:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

Y como era de esperar, esto se imprime Hi!dos veces.

Ahora llega el Sr. Maintainer, que mira el código y piensa: Oye, ese parámetro send_messagees un shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Obviamente, eso se puede cambiar a:

void send_message(const std::shared_ptr<std::string> &msg)

¡Piense en la mejora del rendimiento que esto traerá! (No importa que estemos a punto de enviar un mensaje generalmente grande a través de algún canal, por lo que la mejora del rendimiento será tan pequeña que no podrá medirse).

Pero el problema real es que ahora el código de prueba exhibirá un comportamiento indefinido (en las compilaciones de depuración de Visual C ++ 2010, se bloquea).

El Sr. Maintainer está sorprendido por esto, pero agrega un control defensivo send_messageen un intento de evitar que ocurra el problema:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

Pero, por supuesto, sigue adelante y se bloquea, porque msgnunca es nulo cuando send_messagese llama.

Como digo, con todo el código tan cerca en un ejemplo trivial, es fácil encontrar el error. Pero en programas reales, con relaciones más complejas entre objetos mutables que tienen punteros entre sí, es fácil cometer el error y difícil construir los casos de prueba necesarios para detectar el error.

La solución fácil, en la que desea que una función pueda depender de que shared_ptrcontinúe siendo no nula en todo momento, es que la función asigne su propio verdadero shared_ptr, en lugar de depender de una referencia a un existente shared_ptr.

La desventaja es que la copia de a shared_ptrno es gratuita: incluso las implementaciones "sin bloqueo" tienen que usar una operación entrelazada para respetar las garantías de subprocesamiento. Por lo tanto, puede haber situaciones en las que un programa pueda acelerarse significativamente cambiando a shared_ptren a shared_ptr &. Pero este no es un cambio que se pueda realizar de forma segura en todos los programas. Cambia el significado lógico del programa.

Tenga en cuenta que se produciría un error similar si usáramos en std::stringtodo en lugar de std::shared_ptr<std::string>y en lugar de:

previous_message = 0;

para aclarar el mensaje, dijimos:

previous_message.clear();

Entonces el síntoma sería el envío accidental de un mensaje vacío, en lugar de un comportamiento indefinido. El costo de una copia adicional de una cadena muy grande puede ser mucho más significativo que el costo de copiar una shared_ptr, por lo que la compensación puede ser diferente.

Daniel Earwicker
fuente
16
El shared_ptr que se pasa ya vive en un ámbito, en el sitio de llamada. Es posible que pueda crear un escenario elaborado en el que el código de esta pregunta explote debido a un puntero colgando, pero supongo que tiene problemas más grandes que el parámetro de referencia.
Magnus Hoff
10
Puede almacenarse en un miembro. Puede llamar a algo que suceda para borrar a ese miembro. El objetivo de smart_ptr es evitar tener que coordinar vidas útiles en jerarquías o ámbitos que anidan alrededor de la pila de llamadas, por eso es mejor asumir que las vidas útiles no hacen eso en tales programas.
Daniel Earwicker
7
¡No es realmente mi punto de vista! Si cree que lo que estoy diciendo es algo específico que ver con mi código, es posible que no me haya entendido. Estoy hablando de una implicación inevitable de la razón por la que shared_ptr existe en primer lugar: muchas vidas de objetos no están simplemente relacionadas con llamadas a funciones.
Daniel Earwicker
8
@DanielEarwicker estoy completamente de acuerdo con todos tus puntos y estoy sorprendido por el nivel de oposición. Algo que hace que tus preocupaciones sean aún más relevantes es el enhebrado, cuando esto se involucra, las garantías sobre la validez de un objeto se vuelven mucho más importantes. Buena respuesta.
radman
3
No hace mucho, busqué un error muy grave que se debía a pasar una referencia a un puntero compartido. El código estaba manejando el cambio de estado de un objeto y cuando notó que el estado del objeto había cambiado, lo eliminó de la colección de objetos en el estado anterior y lo movió a la colección de objetos en el nuevo estado. La operación de eliminación destruyó el último puntero compartido al objeto. La función miembro se ha llamado en una referencia al puntero compartido en la colección. Auge. Daniel Earwicker tiene razón.
David Schwartz
115

Me encontré en desacuerdo con la respuesta más votada, así que fui a buscar opiniones de expertos y aquí están. De http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "cuando pasa shared_ptrs, las copias son caras"

Scott Meyers: "Shared_ptr no tiene nada de especial cuando se trata de pasarlo por valor o por referencia. Use exactamente el mismo análisis que usa para cualquier otro tipo definido por el usuario. La gente parece tener esta percepción de que shared_ptr resuelve de alguna manera todos los problemas de gestión, y como es pequeño, es necesariamente económico pasarlo por valor. Tiene que ser copiado, y hay un costo asociado con eso ... es caro pasarlo por valor, así que si puedo salirme con la mía con la semántica adecuada en mi programa, lo pasaré por referencia a const o reference en su lugar "

Herb Sutter: "siempre páselos por referencia a const, y muy ocasionalmente tal vez porque sabe que lo que llamó podría modificar la cosa de la que obtuvo una referencia, tal vez entonces podría pasar por valor ... si los copia como parámetros, oh Dios mío, casi nunca necesitas superar ese recuento de referencias porque de todos modos se mantiene vivo, y deberías pasarlo por referencia, así que haz eso "

Actualización: Herb se ha expandido en esto aquí: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , aunque la moraleja de la historia es que no debería pasar shared_ptrs en absoluto "a menos que desee utilizar o manipular el puntero inteligente en sí, como para compartir o transferir la propiedad".

Jon
fuente
8
¡Buen hallazgo! Es genial ver a dos de los principales expertos en el tema refutando públicamente la sabiduría convencional sobre SO.
Stephan Tolksdorf
3
"Shared_ptr no tiene nada de especial cuando se trata de pasarlo por valor o pasarlo por referencia". Realmente no estoy de acuerdo con esto. ES especial. Personalmente, prefiero ir a lo seguro y recibir un ligero impacto en el rendimiento. Si hay un área de código en particular que necesito optimizar, entonces seguro, miraría los beneficios de rendimiento de shared_ptr pass by const ref.
JasonZ
3
También es interesante notar que, si bien hubo acuerdo sobre el uso excesivo de shared_ptrs, no hubo acuerdo sobre el problema de traspaso de valor frente a referencia.
Nicol Bolas
2
Scott Meyers: "así que si puedo salirse con la suya con la semántica adecuada en mi programa ...", es decir, no contradice mi respuesta en absoluto, lo que señala que averiguar si el cambio de parámetros const &afectará a la semántica solo es fácil en muy programas simples.
Daniel Earwicker
7
Herb Sutter: "muy ocasionalmente, tal vez porque sabes que lo que llamaste podría modificar la cosa de la que obtuviste una referencia". Nuevamente, esa pequeña exención para un caso menor, por lo que no contradice mi respuesta. La pregunta sigue siendo: ¿cómo sabe que es seguro usar una referencia constante? Muy fácil de probar en un programa simple, no tan fácil en un programa complejo. Pero bueno, esto es C ++, por lo que favorecemos la microoptimización prematura sobre casi todas las demás preocupaciones de ingeniería, ¿verdad? :)
Daniel Earwicker
22

No recomendaría esta práctica a menos que usted y los otros programadores con los que trabaja sepan realmente lo que están haciendo.

Primero, no tiene idea de cómo podría evolucionar la interfaz de su clase y desea evitar que otros programadores hagan cosas malas. Pasar un shared_ptr por referencia no es algo que un programador debería esperar ver, porque no es idiomático y eso facilita su uso incorrecto. Programar a la defensiva: dificulta el uso incorrecto de la interfaz. Pasar por referencia solo generará problemas más adelante.

En segundo lugar, no optimice hasta que sepa que esta clase en particular será un problema. Primero perfil, y luego si su programa realmente necesita el impulso dado al pasar por referencia, entonces tal vez. De lo contrario, no se preocupe por las cosas pequeñas (es decir, las N instrucciones adicionales que se necesitan para pasar por valor) en lugar de preocuparse por el diseño, las estructuras de datos, los algoritmos y la capacidad de mantenimiento a largo plazo.

Mark Kegel
fuente
Aunque la respuesta de litb es técnicamente correcta, nunca subestimes la "pereza" de los programadores (¡yo también soy perezoso!). La respuesta de littlenag es mejor, que una referencia a un shared_ptr será inesperada y posiblemente (probablemente) una optimización innecesaria que haga que el mantenimiento futuro sea más desafiante.
Netjeff
18

Sí, tomar una referencia está bien. No tiene la intención de otorgar propiedad compartida al método; solo quiere trabajar con él. También puede tomar una referencia para el primer caso, ya que la copia de todos modos. Pero para el primer caso, toma posesión. Existe este truco para copiarlo solo una vez:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

También debe copiar cuando lo devuelva (es decir, no devuelva una referencia). Porque su clase no sabe qué está haciendo el cliente con él (podría almacenar un puntero y luego ocurre el big bang). Si más adelante resulta que es un cuello de botella (¡primer perfil!), Aún puede devolver una referencia.


Editar : Por supuesto, como señalan otros, esto solo es cierto si conoce su código y sabe que no restablece el puntero compartido pasado de alguna manera. En caso de duda, pase por valor.

Johannes Schaub - litb
fuente
11

Es sensato pasar shared_ptrpor alto const&. Es probable que no cause problemas (excepto en el improbable caso de que el referenciado shared_ptrse elimine durante la llamada a la función, como lo detalla Earwicker) y probablemente será más rápido si pasa muchos de estos. Recuerda; el valor predeterminado boost::shared_ptres seguro para subprocesos, por lo que copiarlo incluye un incremento seguro para subprocesos.

Intente usar en const&lugar de solo &, porque es posible que los objetos temporales no se pasen por una referencia que no sea constante. (Aunque una extensión de idioma en MSVC le permite hacerlo de todos modos)

Magnus Hoff
fuente
3
Sí, siempre uso referencias const, simplemente olvidé ponerlo en mi ejemplo. De todos modos, MSVC permite enlazar referencias no constantes a temporales no por un error, sino porque por defecto tiene la propiedad "C / C ++ -> Idioma -> Deshabilitar extensión de idioma" establecida en "NO". Habilítelo y no los compilará ...
abigagli
abigagli: ¿En serio? ¡Dulce! Haré cumplir esto en el trabajo, mañana a primera hora;)
Magnus Hoff
10

En el segundo caso, hacer esto es más simple:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Puedes llamarlo como

only_work_with_sp(*sp);
Greg Rogers
fuente
3
Si adopta la convención para usar referencias a objetos cuando no necesita tomar una copia del puntero, también sirve para documentar su intención. También le da la oportunidad de usar una referencia constante.
Mark Ransom
Sí, estoy de acuerdo en el uso de referencias a objetos como un medio para expresar que la función llamada no "recuerda" nada sobre ese objeto. Por lo general, uso argumentos formales de puntero si la función "realiza un seguimiento" del objeto
abigagli
3

Evitaría una referencia "simple" a menos que la función pueda modificar explícitamente el puntero.

A const &puede ser una microoptimización sensata al llamar a funciones pequeñas, por ejemplo, para permitir más optimizaciones, como insertar algunas condiciones. Además, el incremento / decremento, ya que es seguro para subprocesos, es un punto de sincronización. Sin embargo, no esperaría que esto hiciera una gran diferencia en la mayoría de los escenarios.

Generalmente, debe usar el estilo más simple a menos que tenga motivos para no hacerlo. Luego, use el const &consistentemente o agregue un comentario sobre por qué si lo usa solo en algunos lugares.

Peterchen
fuente
3

Yo recomendaría pasar un puntero compartido por referencia constante, una semántica de que la función que se pasa con el puntero NO posee el puntero, que es un lenguaje limpio para los desarrolladores.

El único error es que en los programas de subprocesos múltiples, el objeto al que apunta el puntero compartido se destruye en otro subproceso. Por lo tanto, es seguro decir que usar la referencia constante del puntero compartido es seguro en un programa de un solo subproceso.

Pasar un puntero compartido por una referencia no constante es a veces peligroso: la razón es que las funciones de intercambio y restablecimiento que la función puede invocar en el interior para destruir el objeto que aún se considera válido después de que la función regrese.

No se trata de una optimización prematura, supongo, se trata de evitar un desperdicio innecesario de ciclos de CPU cuando tienes claro lo que quieres hacer y tus compañeros desarrolladores han adoptado firmemente el lenguaje de codificación.

Solo mis 2 centavos :-)

zmqiu
fuente
1
Vea el comentario anterior de David Schwartz "... Busqué un error muy grave que se debía a pasar una referencia a un puntero compartido. El código estaba manejando el cambio de estado de un objeto y cuando notó que el estado del objeto había cambiado, lo eliminó de la colección de objetos en el estado anterior y lo movió a la colección de objetos en el nuevo estado. La operación de eliminación destruyó el último puntero compartido al objeto. La función miembro se había llamado en una referencia al puntero compartido en la colección. Boom ... "
Jason Harrison
3

Parece que todos los pros y los contras aquí en realidad se pueden generalizar a CUALQUIER tipo pasado por referencia, no solo shared_ptr. En mi opinión, debes conocer la semántica de pasar por referencia, const referencia y valor y utilizarla correctamente. Pero no hay absolutamente nada de malo en pasar shared_ptr por referencia, a menos que piense que todas las referencias son malas ...

Para volver al ejemplo:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

¿Cómo sabes que sp.do_something()no explotará debido a un puntero colgando?

La verdad es que, shared_ptr o no, const o no, esto podría suceder si tiene un defecto de diseño, como compartir directa o indirectamente la propiedad spentre subprocesos, mal uso de un objeto que sí delete this, tiene una propiedad circular u otros errores de propiedad.

Arenoso
fuente
2

Una cosa que no he visto mencionada todavía es que cuando pasa punteros compartidos por referencia, pierde la conversión implícita que obtiene si desea pasar un puntero compartido de clase derivada a través de una referencia a un puntero compartido de clase base.

Por ejemplo, este código producirá un error, pero funcionará si lo cambia test()para que el puntero compartido no se pase por referencia.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
Malvino
fuente
1

Asumiré que está familiarizado con la optimización prematura y lo está preguntando con fines académicos o porque ha aislado algún código preexistente que tiene un rendimiento inferior.

Pasar por referencia está bien

Pasar por referencia constante es mejor y generalmente se puede usar, ya que no fuerza la constancia en el objeto apuntado.

Estás no en riesgo de perder el puntero debido al uso de una referencia. Esa referencia es evidencia de que tiene una copia del puntero inteligente anteriormente en la pila y solo un subproceso posee una pila de llamadas, por lo que la copia preexistente no desaparecerá.

El uso de referencias suele ser más eficaz por las razones que menciona, pero no está garantizado . Recuerde que eliminar la referencia a un objeto también puede requerir trabajo. Su escenario ideal de uso de referencia sería si su estilo de codificación involucra muchas funciones pequeñas, donde el puntero pasaría de una función a otra antes de ser utilizado.

Siempre debe evitar almacenar su puntero inteligente como referencia. Su Class::take_copy_of_sp(&sp)ejemplo muestra el uso correcto para eso.

Drew Dormann
fuente
1
"No corre el riesgo de perder el puntero debido al uso de una referencia. Esa referencia es evidencia de que tiene una copia del puntero inteligente anteriormente en la pila" ¿O un miembro de datos ...?
Daniel Earwicker
Considere la magia de boost :: thread y boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = new boost :: thread (functionPointer); ... eliminar sharedPtrInstance
Jason Harrison
1

Suponiendo que no nos preocupa la corrección const (o más, quiere decir permitir que las funciones puedan modificar o compartir la propiedad de los datos que se pasan), pasar un boost :: shared_ptr por valor es más seguro que pasarlo por referencia como permitimos que el boost :: shared_ptr original controle su propia vida. Considere los resultados del siguiente código ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

En el ejemplo anterior, vemos que pasar sharedA por referencia permite a FooTakesReference restablecer el puntero original, lo que reduce su recuento de uso a 0, destruyendo sus datos. FooTakesValue , sin embargo, no puede restablecer el puntero original, lo que garantiza que los datos de sharedB aún sean utilizables. Cuando inevitablemente aparece otro desarrollador e intenta aprovechar la frágil existencia de sharedA , se produce el caos. El afortunado desarrollador de sharedB , sin embargo, se va a casa temprano porque todo está bien en su mundo.

La seguridad del código, en este caso, supera con creces cualquier mejora de velocidad que crea la copia. Al mismo tiempo, boost :: shared_ptr está destinado a mejorar la seguridad del código. Será mucho más fácil pasar de una copia a una referencia, si algo requiere este tipo de optimización de nicho.

Kit10
fuente
1

Sandy escribió: "Parece que todos los pros y los contras aquí en realidad pueden generalizarse a CUALQUIER tipo pasado por referencia, no solo shared_ptr".

Es cierto hasta cierto punto, pero el objetivo de utilizar shared_ptr es eliminar las preocupaciones sobre la vida útil de los objetos y dejar que el compilador se encargue de ello. Si va a pasar un puntero compartido por referencia y permitirá que los clientes de su objeto contado de referencia llamen a métodos no constantes que podrían liberar los datos del objeto, entonces usar un puntero compartido es casi inútil.

Escribí "casi" en esa oración anterior porque el rendimiento puede ser una preocupación y "podría" estar justificado en casos excepcionales, pero también evitaría este escenario y buscaría todas las otras posibles soluciones de optimización yo mismo, como buscar seriamente al agregar otro nivel de indirección, evaluación perezosa, etc.

El código que existe más allá de su autor, o incluso publica su memoria del autor, que requiere suposiciones implícitas sobre el comportamiento, en particular el comportamiento sobre la vida útil de los objetos, requiere documentación clara, concisa y legible, ¡y muchos clientes no lo leerán de todos modos! La simplicidad casi siempre triunfa sobre la eficiencia, y casi siempre hay otras formas de ser eficiente. Si realmente necesita pasar valores por referencia para evitar la copia profunda mediante los constructores de copia de sus objetos contados de referencia (y el operador igual), entonces tal vez debería considerar formas de hacer que los datos copiados en profundidad sean punteros contados de referencia que pueden ser copiado rápidamente. (Por supuesto, ese es solo un escenario de diseño que podría no aplicarse a su situación).

Bill H
fuente
1

Solía ​​trabajar en un proyecto en el que el principio era muy sólido acerca de pasar punteros inteligentes por valor. Cuando me pidieron que hiciera un análisis de rendimiento, descubrí que para el incremento y la disminución de los contadores de referencia de los punteros inteligentes, la aplicación gasta entre el 4 y el 6% del tiempo de procesador utilizado.

Si desea pasar los indicadores inteligentes por valor solo para evitar problemas en casos extraños, como lo describe Daniel Earwicker, asegúrese de comprender el precio que paga por ello.

Si decide ir con una referencia, la razón principal para usar la referencia const es hacer posible tener una conversión ascendente implícita cuando necesita pasar un puntero compartido a un objeto de la clase que hereda la clase que usa en la interfaz.

gsf
fuente
0

Además de lo que dijo litb, me gustaría señalar que probablemente se deba pasar por la referencia constante en el segundo ejemplo, de esa manera está seguro de no modificarlo accidentalmente.

Leon Timmermans
fuente
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. pasar por valor:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. pasar por referencia:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. pasar por puntero:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
Dylan Chen
fuente
0

Cada pieza de código debe tener algún sentido. Si pasa un puntero compartido por valor en todas partes de la aplicación, esto significa "No estoy seguro de lo que está sucediendo en otros lugares, por lo tanto, prefiero la seguridad bruta ". Esto no es lo que yo llamo una buena señal de confianza para otros programadores que podrían consultar el código.

De todos modos, incluso si una función obtiene una referencia constante y no está "seguro", aún puede crear una copia del puntero compartido en el encabezado de la función, para agregar una referencia fuerte al puntero. Esto también podría verse como una pista sobre el diseño ("el puntero podría modificarse en otro lugar").

Así que sí, en mi opinión, el valor predeterminado debería ser " pasar por referencia constante ".

Philippe
fuente