¿Hay alguna forma preferida de devolver múltiples valores de una función C ++? Por ejemplo, imagine una función que divide dos enteros y devuelve tanto el cociente como el resto. Una forma que veo comúnmente es utilizar parámetros de referencia:
void divide(int dividend, int divisor, int& quotient, int& remainder);
Una variación es devolver un valor y pasar el otro a través de un parámetro de referencia:
int divide(int dividend, int divisor, int& remainder);
Otra forma sería declarar una estructura para contener todos los resultados y devolver eso:
struct divide_result {
int quotient;
int remainder;
};
divide_result divide(int dividend, int divisor);
¿Se prefiere generalmente una de estas formas o hay otras sugerencias?
Editar: en el código del mundo real, puede haber más de dos resultados. También pueden ser de diferentes tipos.
std::tuple
.std::tie
stackoverflow.com/a/2573822/502144En C ++ 11 puedes:
En C ++ 17:
o con estructuras:
fuente
struct
línea fuera del cuerpo de la función y reemplace laauto
función de retornoresult
.divide
en un archivo cpp separado? Me sale el errorerror: use of ‘auto divide(int, int)’ before deduction of ‘auto’
. ¿Cómo puedo solucionar esto?Personalmente, generalmente no me gustan los parámetros de retorno por varias razones:
También tengo algunas reservas sobre la técnica par / tupla. Principalmente, a menudo no hay un orden natural para los valores de retorno. ¿Cómo sabe el lector del código si result.first es el cociente o el resto? Y el implementador podría cambiar el orden, lo que rompería el código existente. Esto es especialmente insidioso si los valores son del mismo tipo para que no se genere ningún error o advertencia del compilador. En realidad, estos argumentos se aplican también a los parámetros de retorno.
Aquí hay otro ejemplo de código, este un poco menos trivial:
¿Esto imprime velocidad y rumbo, o rumbo y velocidad? No es obvio
Compare a esto:
Creo que esto está más claro.
Así que creo que mi primera opción en general es la técnica de estructura. La idea par / tupla es probablemente una gran solución en ciertos casos. Me gustaría evitar los parámetros de retorno cuando sea posible.
fuente
struct
gustaVelocity
es buena. Sin embargo, una preocupación es que contamina el espacio de nombres. Supongo que con C ++ 11,struct
puede tener un nombre de tipo largo, y uno puede usarauto result = calculateResultingVelocity(...)
.struct { int a, b; } my_func();
. Esto podría ser utilizado como esto:auto result = my_func();
. Pero C ++ no permite esto: "los nuevos tipos pueden no estar definidos en un tipo de retorno". Así que tengo que crear estructuras comostruct my_func_result_t
...auto
, por lo queauto result = my_func();
se puede obtener de manera trivial.std :: pair es esencialmente su solución de estructura, pero ya está definida para usted y lista para adaptarse a dos tipos de datos.
fuente
Depende completamente de la función real y el significado de los valores múltiples, y sus tamaños:
fuente
La solución OO para esto es crear una clase de razón. No tomaría ningún código adicional (ahorraría algo), sería significativamente más limpio / claro y le daría algunas refactorizaciones adicionales que le permitirán limpiar el código fuera de esta clase también.
En realidad, creo que alguien recomendó devolver una estructura, que está lo suficientemente cerca pero oculta la intención de que esta sea una clase completamente pensada con un constructor y algunos métodos, de hecho, el "método" que mencionó originalmente (como devolver el probablemente debería ser un miembro de esta clase que devuelva una instancia de sí mismo.
Sé que su ejemplo fue solo un "Ejemplo", pero el hecho es que, a menos que su función esté haciendo mucho más de lo que debería hacer cualquier función, si desea que devuelva múltiples valores, es muy probable que le falte un objeto.
No tengas miedo de crear estas clases pequeñas para hacer pequeños trabajos, esa es la magia de OO, terminas desglosando hasta que cada método sea muy pequeño y simple y cada clase pequeña y comprensible.
Otra cosa que debería haber sido un indicador de que algo estaba mal: en OO esencialmente no tienes datos: OO no se trata de pasar datos, una clase necesita administrar y manipular sus propios datos internamente, cualquier paso de datos (incluidos los accesos) es una señal de que puede que tenga que repensar algo ...
fuente
Existe un precedente para devolver estructuras en el estándar C (y, por lo tanto, C ++) con las funciones
div
,ldiv
(y, en C99,lldiv
) de<stdlib.h>
(o<cstdlib>
).La 'combinación de valor de retorno y parámetros de retorno' suele ser la menos limpia.
Hacer que una función devuelva un estado y devuelva datos a través de parámetros de retorno es sensato en C; es menos obvio en C ++, donde podría usar excepciones para transmitir información de fallas.
Si hay más de dos valores de retorno, probablemente sea mejor un mecanismo similar a una estructura.
fuente
Con C ++ 17 también puede devolver uno o más valores inamovibles / no copiables (en ciertos casos). La posibilidad de devolver tipos inmóviles viene a través de la nueva optimización del valor de retorno garantizado, y se compone muy bien con los agregados , y lo que se puede llamar constructores con plantilla .
Lo bonito de esto es que se garantiza que no causará ninguna copia o movimiento. Puedes hacer el ejemplo
many
struct también sea variable. Más detalles:Devolución de agregados variadic (struct) y sintaxis para la 'guía de deducción de construcción' de plantilla variadic C ++ 17
fuente
Hay muchas formas de devolver múltiples parámetros. Voy a estar exhausto.
Usar parámetros de referencia:
usar parámetros de puntero:
que tiene la ventaja de que tienes que hacer un
&
llamada en el sitio de la llamada, posiblemente alertando a las personas de que es un parámetro fuera de lugar.Escribe una plantilla y úsala:
entonces podemos hacer:
Y todo está bien.
foo
ya no puede leer ningún valor pasado como bonificación.Se pueden utilizar otras formas de definir un lugar en el que se pueden colocar datos
out
. Una devolución de llamada para colocar cosas en algún lugar, por ejemplo.Podemos devolver una estructura:
whick funciona bien en todas las versiones de C ++, y en c ++ 17 esto también permite:
a costo cero Los parámetros ni siquiera se pueden mover gracias a la elisión garantizada.
Podríamos devolver un
std::tuple
:que tiene el inconveniente de que los parámetros no tienen nombre. Esto permite elc ++ 17:
también. Antes dec ++ 17 en su lugar podemos hacer:
lo cual es un poco más incómodo. Sin embargo, la elisión garantizada no funciona aquí.
Al entrar en territorio extraño (¡y esto es después
out<>
!), Podemos usar el estilo de pase de continuación:y ahora las personas que llaman hacen:
Una ventaja de este estilo es que puede devolver un número arbitrario de valores (con tipo uniforme) sin tener que administrar la memoria:
la
value
devolución de llamada podría llamarse 500 veces cuando ustedget_all_values( [&](int value){} )
.Por pura locura, incluso podría usar una continuación en la continuación.
cuyo uso se parece a:
lo que permitiría muchas relaciones entre
result
yother
.De nuevo con los valores de California, podemos hacer esto:
aquí, llamamos a la devolución de llamada con una serie de resultados. Incluso podemos hacer esto repetidamente.
Con esto, puede tener una función que pase eficientemente megabytes de datos sin hacer ninguna asignación fuera de la pila.
Ahora,
std::function
es un poco pesado para esto, ya que estaríamos haciendo esto en entornos de cero sobrecarga sin asignación. Así que querríamos unfunction_view
que nunca asigne.Otra solución es:
donde en lugar de tomar la devolución de llamada e invocarla, en su
foo
lugar , devuelve una función que toma la devolución de llamada.foo (7) ([&] (int resultado, int otro_resultado) {/ * código * /}); esto rompe los parámetros de salida de los parámetros de entrada al tener corchetes separados.
Con
variant
yc ++ 20corutinas, podría hacerfoo
un generador de una variante de los tipos de retorno (o solo el tipo de retorno). La sintaxis aún no está arreglada, por lo que no daré ejemplos.En el mundo de las señales y las ranuras, una función que expone un conjunto de señales:
le permite crear uno
foo
que funciona de forma asíncrona y difunde el resultado cuando finaliza.En esta línea, tenemos una variedad de técnicas de canalización, donde una función no hace algo, sino que organiza los datos para que se conecten de alguna manera, y la acción es relativamente independiente.
entonces este código no hace nada hasta que
int_source
tenga números enteros para proporcionarlo. Cuando lo haga,int_dest1
yint_dest2
comience a recibir los resultados.fuente
auto&&[result, other_result]=foo();
funciones que devuelven tuplas y estructuras. ¡Gracias!Use una estructura o una clase para el valor de retorno. Usar
std::pair
puede funcionar por ahora, peroEs probable que devolver una estructura con nombres de variables de miembro autodocumentados sea menos propenso a errores para cualquiera que use su función. Al ponerme el sombrero de compañero de trabajo por un momento, su
divide_result
estructura es fácil para mí, un usuario potencial de su función, para comprender inmediatamente después de 2 segundos. Jugar con parámetros de salida o pares y tuplas misteriosas llevaría más tiempo leerlo y puede usarse incorrectamente. Y lo más probable, incluso después de usar la función varias veces, todavía no recordaré el orden correcto de los argumentos.fuente
Si su función devuelve un valor por referencia, el compilador no puede almacenarlo en un registro cuando llama a otras funciones porque, en teoría, la primera función puede guardar la dirección de la variable que se le pasa en una variable accesible globalmente, y cualquier función llamada posteriormente puede cámbielo, para que el compilador (1) guarde el valor de los registros en la memoria antes de llamar a otras funciones y (2) vuelva a leerlo cuando sea necesario de la memoria nuevamente después de cualquiera de esas llamadas.
Si regresa por referencia, la optimización de su programa sufrirá
fuente
Aquí, estoy escribiendo un programa que está devolviendo múltiples valores (más de dos valores) en c ++. Este programa es ejecutable en c ++ 14 (G ++ 4.9.2). El programa es como una calculadora.
Por lo tanto, puede comprender claramente que de esta manera puede devolver múltiples valores de una función. usando std :: pair solo se pueden devolver 2 valores, mientras que std :: tuple puede devolver más de dos valores.
fuente
auto
tipo de retornocal
para hacer esto aún más limpio. (OMI)Tiendo a usar out-vals en funciones como esta, porque me apego al paradigma de una función que devuelve códigos de éxito / error y me gusta mantener las cosas uniformes.
fuente
Las alternativas incluyen matrices, generadores e inversión de control , pero ninguna es apropiada aquí.
Algunos (por ejemplo, Microsoft en Win32 histórico) tienden a usar parámetros de referencia por simplicidad, porque está claro quién asigna y cómo se verá en la pila, reduce la proliferación de estructuras y permite un valor de retorno separado para el éxito.
Los programadores "puros" prefieren la estructura, suponiendo que sea el valor de la función (como es el caso aquí), en lugar de algo que la función toca incidentalmente. Si tuviera un procedimiento más complicado, o algo con estado, probablemente usaría referencias (suponiendo que tenga una razón para no usar una clase).
fuente
Yo diría que no hay un método preferido, todo depende de lo que va a hacer con la respuesta. Si los resultados se van a usar juntos en un procesamiento posterior, entonces las estructuras tienen sentido, si no tendré que pasarlas como referencias individuales a menos que la función se vaya a usar en una declaración compuesta:
x = divide( x, y, z ) + divide( a, b, c );
A menudo elijo pasar 'estructuras' por referencia en la lista de parámetros en lugar de tener que pasar por encima de la copia la devolución de una nueva estructura (pero esto está sudando las cosas pequeñas).
void divide(int dividend, int divisor, Answer &ans)
¿Son confusos nuestros parámetros? Un parámetro enviado como referencia sugiere que el valor va a cambiar (a diferencia de una referencia constante). El nombramiento sensible también elimina la confusión.
fuente
¿Por qué insiste en una función con múltiples valores de retorno? Con OOP puede usar una clase que ofrece una función regular con un único valor de retorno y cualquier cantidad de "valores de retorno" adicionales, como se muestra a continuación. La ventaja es que la persona que llama tiene la opción de mirar a los miembros de datos adicionales, pero no está obligado a hacerlo. Este es el método preferido para llamadas a redes o bases de datos complicadas, donde se puede necesitar mucha información de devolución adicional en caso de que ocurran errores.
Para responder a su pregunta original, este ejemplo tiene un método para devolver el cociente, que es lo que la mayoría de las personas que llaman pueden necesitar, y además, después de la llamada al método, puede obtener el resto como miembro de datos.
fuente
en lugar de devolver múltiples valores, simplemente devuelva uno de ellos y haga una referencia de otros en la función requerida para, por ejemplo:
fuente
Boost tuple sería mi opción preferida para un sistema generalizado de devolución de más de un valor de una función.
Posible ejemplo:
fuente
Podemos declarar la función de modo que devuelva una variable definida por el usuario de tipo estructura o un puntero a ella. Y por la propiedad de una estructura, sabemos que una estructura en C puede contener múltiples valores de tipos asimétricos (es decir, una variable int, cuatro variables char, dos variables flotantes, etc.)
fuente
Solo lo haría por referencia si solo son unos pocos valores de retorno, pero para tipos más complejos también puede hacerlo así:
use "static" para limitar el alcance del tipo de retorno a esta unidad de compilación si solo pretende ser un tipo de retorno temporal.
Definitivamente, esta no es la forma más bonita de hacerlo, pero funcionará.
fuente
Aquí hay un ejemplo completo de este tipo de solución de problemas
fuente