Devuelve punteros inteligentes por valor.
Como ha dicho, si lo devuelve por referencia, no aumentará correctamente el recuento de referencias, lo que abre el riesgo de eliminar algo en el momento inadecuado. Eso por sí solo debería ser razón suficiente para no regresar por referencia. Las interfaces deben ser sólidas.
La preocupación por el costo es hoy en día discutible gracias a la optimización del valor de retorno (RVO), por lo que no incurrirá en una secuencia de incremento-incremento-decremento o algo así en los compiladores modernos. Entonces, la mejor manera de devolver un shared_ptr
es simplemente regresar por valor:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
Esta es una oportunidad RVO obvia para los compiladores modernos de C ++. Sé a ciencia cierta que los compiladores de Visual C ++ implementan RVO incluso cuando todas las optimizaciones están desactivadas. Y con la semántica de movimientos de C ++ 11, esta preocupación es aún menos relevante. (Pero la única forma de estar seguro es perfilar y experimentar).
Si aún no está convencido, Dave Abrahams tiene un artículo que argumenta a favor de la devolución por valor. Reproduzco un fragmento aquí; Le recomiendo que lea el artículo completo:
Sea honesto: ¿cómo le hace sentir el siguiente código?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Francamente, aunque debería saberlo mejor, me pone nervioso. En principio, cuando get_names()
regrese, tenemos que copiar una vector
de string
s. Luego, necesitamos copiarlo nuevamente cuando inicializamos
names
, y necesitamos destruir la primera copia. Si hay N string
s en el vector, cada copia podría requerir hasta N + 1 asignaciones de memoria y una gran cantidad de accesos de datos no amigables con la caché> a medida que se copia el contenido de la cadena.
En lugar de enfrentarme a ese tipo de ansiedad, a menudo recurro al paso por referencia para evitar copias innecesarias:
get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );
Desafortunadamente, este enfoque está lejos de ser ideal.
- El código creció un 150%
- Hemos tenido que abandonar
const
porque estamos mutando nombres.
- Como a los programadores funcionales les gusta recordarnos, la mutación hace que el código sea más complejo para razonar al socavar la transparencia referencial y el razonamiento ecuacional.
- Ya no tenemos una semántica de valores estricta para los nombres.
Pero, ¿es realmente necesario estropear nuestro código de esta manera para ganar eficiencia? Afortunadamente, la respuesta resulta ser no (y especialmente no si está usando C ++ 0x).
cout << "Hello World!";
declaración en un constructor de copia y predeterminado, no verá dosHello World!
s cuando RVO esté en vigor. Sin embargo, esto no debería ser un problema para los punteros inteligentes diseñados correctamente, incluso con la sincronización.Con respecto a cualquier puntero inteligente (no solo shared_ptr), no creo que sea aceptable devolver una referencia a uno, y dudaría mucho en pasarlos por referencia o puntero sin formato. ¿Por qué? Porque no puede estar seguro de que no se copiará superficialmente a través de una referencia más adelante. Su primer punto define la razón por la que esto debería ser una preocupación. Esto puede suceder incluso en un entorno de un solo subproceso. No necesita acceso simultáneo a los datos para poner semántica de copia incorrecta en sus programas. Realmente no controlas lo que hacen tus usuarios con el puntero una vez que lo pasas, así que no fomentes el mal uso dando a los usuarios de la API suficiente cuerda para ahorcarse.
En segundo lugar, observe la implementación de su puntero inteligente, si es posible. La construcción y la destrucción deberían ser casi insignificantes. Si esta sobrecarga no es aceptable, ¡no use un puntero inteligente! Pero más allá de esto, también necesitará examinar la arquitectura de concurrencia que tiene, porque el acceso mutuamente exclusivo al mecanismo que rastrea los usos del puntero lo ralentizará más que la mera construcción del objeto shared_ptr.
Editar, 3 años después: con la llegada de las características más modernas en C ++, modificaría mi respuesta para que acepte mejor los casos en los que simplemente ha escrito una lambda que nunca vive fuera del alcance de la función de llamada, y no lo es copiado en otro lugar. Aquí, si desea ahorrar la mínima sobrecarga de copiar un puntero compartido, sería justo y seguro. ¿Por qué? Porque puede garantizar que la referencia nunca será mal utilizada.
fuente