valores valor usar una retornar retornan que puede mas funciones funcion devolver con como arreglos arreglo c++ c++11 coding-style return-value-optimization

valor - En C++, ¿sigue siendo una mala práctica devolver un vector de una función?



funciones que retornan valores en c++ (7)

Al menos IMO, generalmente es una idea pobre, pero no por razones de eficiencia. Es una idea pobre porque la función en cuestión normalmente debe escribirse como un algoritmo genérico que produce su salida a través de un iterador. Casi cualquier código que acepte o devuelva un contenedor en lugar de operar en iteradores se debe considerar sospechoso.

No me malinterpreten: a veces tiene sentido pasar por objetos similares a colecciones (por ejemplo, cadenas), pero para el ejemplo citado, consideraría pasar o devolver al vector una mala idea.

Versión corta: es común devolver objetos grandes, como vectores / matrices, en muchos lenguajes de programación. ¿Este estilo ahora es aceptable en C ++ 0x si la clase tiene un constructor de movimiento, o los programadores de C ++ lo consideran extraño / feo / abominable?

Versión larga: ¿ en C ++ 0x esto todavía se considera mala forma?

std::vector<std::string> BuildLargeVector(); ... std::vector<std::string> v = BuildLargeVector();

La versión tradicional se vería así:

void BuildLargeVector(std::vector<std::string>& result); ... std::vector<std::string> v; BuildLargeVector(v);

En la versión más reciente, el valor devuelto por BuildLargeVector es un valor r, por lo que v se construiría usando el constructor de movimiento de std::vector , suponiendo que (R) no se lleve a cabo.

Incluso antes de C ++ 0x, la primera forma a menudo sería "eficiente" debido a (N) RVO. Sin embargo, (N) RVO queda a criterio del compilador. Ahora que tenemos referencias de valor real, se garantiza que no se realizará ninguna copia en profundidad.

Editar : La pregunta realmente no es sobre la optimización. Ambas formas mostradas tienen un rendimiento casi idéntico en los programas del mundo real. Mientras que, en el pasado, la primera forma podría haber tenido peor orden de magnitud de rendimiento. Como resultado, la primera forma fue un gran olor a código en la programación de C ++ durante mucho tiempo. Ya no, espero?


Dave Abrahams tiene un análisis bastante completo de la velocidad de los valores de aprobación / devolución .

Respuesta corta, si necesita devolver un valor, regrese un valor. No use referencias de salida porque el compilador lo hace de todos modos. Por supuesto, hay advertencias, por lo que debe leer ese artículo.


De hecho, desde C ++ 11, el costo de copiar el std::vector se ha ido en la mayoría de los casos.

Sin embargo, se debe tener en cuenta que el costo de construir el nuevo vector (luego destruirlo ) aún existe, y usar parámetros de salida en lugar de devolver por valor sigue siendo útil cuando se desea reutilizar la capacidad del vector. Esto está documentado como una excepción en F.20 de las Directrices básicas de C ++.

Comparar:

std::vector<int> BuildLargeVector(int i) { return std::vector<int>(1000000, i); } int main() { for (int i = 0; i < 100; ++i) { std::vector<int> v = BuildLargeVector(i); // [...] do smth with v } }

Con:

void BuildLargeVector(/*out*/ std::vector<int>& v, int i) { v.assign(1000000, i); } int main() { std::vector<int> v; for (int i = 0; i < 100; ++i) { BuildLargeVector(/*out*/ v, i); // [...] do smth with v } }

En el primer ejemplo, hay muchas asignaciones / desasignaciones dinámicas innecesarias que suceden, que se evitan en el segundo ejemplo al usar un parámetro de salida de la vieja manera, reutilizando la memoria ya asignada. Si esta optimización vale la pena o no depende del costo relativo de la asignación / desasignación en comparación con el costo de computar / mutar los valores.


La esencia es:

Copiar Elision y RVO pueden evitar las "copias de miedo" (el compilador no está obligado a implementar estas optimizaciones, y en algunas situaciones no se puede aplicar)

Las referencias C ++ 0x RValue permiten una implementación de cadena / vector que garantiza eso.

Si puede abandonar implementaciones de compiladores / STL más antiguas, devuelva los vectores libremente (y asegúrese de que sus propios objetos también lo admitan). Si su base de código necesita admitir compiladores "menores", manténgase en el viejo estilo.

Desafortunadamente, eso tiene una gran influencia en sus interfaces. Si C ++ 0x no es una opción, y necesita garantías, puede utilizar objetos en lugar de referencia contados o de escritura en algunos escenarios. Sin embargo, tienen inconvenientes con el multihilo.

(Deseo que una sola respuesta en C ++ sea simple y directa y sin condiciones).


Si el rendimiento es un problema real, debe tener en cuenta que la semántica de movimiento no siempre es más rápida que la copia. Por ejemplo, si tiene una cadena que utiliza la optimización de cadena pequeña, entonces para cadenas pequeñas, un constructor de movimientos debe hacer la misma cantidad de trabajo que un constructor de copia normal.


Solo para criticar un poco: no es común en muchos lenguajes de programación devolver matrices de funciones. En la mayoría de ellos, se devuelve una referencia a la matriz. En C ++, la analogía más cercana sería regresar boost::shared_array


Todavía creo que es una mala práctica, pero vale la pena señalar que mi equipo usa MSVC 2008 y GCC 4.1, por lo que no estamos utilizando los últimos compiladores.

Anteriormente, muchos de los hotspots mostrados en vtune con MSVC 2008 se reducían a la copia de cadenas. Teníamos un código como este:

String Something::id() const { return valid() ? m_id: ""; }

... tenga en cuenta que utilizamos nuestro propio tipo String (esto fue necesario porque proporcionamos un kit de desarrollo de software donde los escritores de plugins podrían usar diferentes compiladores y, por lo tanto, implementaciones diferentes e incompatibles de std :: string / std :: wstring).

Realicé un cambio simple en respuesta a la sesión de generación de perfiles de muestreo del gráfico de llamadas que muestra String :: String (const String &) que ocupará una cantidad significativa de tiempo. Los métodos como en el ejemplo anterior fueron los que más contribuyeron (en realidad, la sesión de generación de perfiles mostró que la asignación de memoria y la desasignación eran uno de los puntos de acceso más importantes, y que el constructor de copias String era el principal contribuyente para las asignaciones).

El cambio que hice fue simple:

static String null_string; const String& Something::id() const { return valid() ? m_id: null_string; }

Sin embargo, esto hizo un mundo de diferencia! El punto de conexión desapareció en las siguientes sesiones de perfiladores, y además de esto, realizamos muchas pruebas unitarias para realizar un seguimiento del rendimiento de nuestra aplicación. Todos los tipos de tiempos de prueba de rendimiento disminuyeron significativamente después de estos cambios simples.

Conclusión: no estamos utilizando los últimos compiladores absolutos, pero todavía no podemos depender del compilador que optimice la copia para devolver el valor de forma confiable (al menos no en todos los casos). Ese puede no ser el caso para aquellos que usan compiladores más nuevos como MSVC 2010. Espero con ansias cuándo podemos usar C ++ 0x y simplemente usar referencias rvalue y no tener que preocuparnos de que estamos pesimizando nuestro código volviendo complejo clases por valor

[Editar] Como Nate señaló, RVO se aplica a devolver los temporales creados dentro de una función. En mi caso, no existían tales temporales (a excepción de la rama inválida donde construimos una cadena vacía) y, por lo tanto, RVO no habría sido aplicable.