una - devolver valores multiples c++
Devolver múltiples valores desde una función de C++ (18)
¿Hay alguna forma de devolver valores múltiples desde una función de C ++? Por ejemplo, imagine una función que divide dos enteros y devuelve tanto el cociente como el resto. Una forma en que comúnmente veo es usar 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.
¿Por qué insistes 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 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 la base de datos complicada o llamadas de red, donde puede necesitar mucha información de devolución adicional en caso de que se produzcan 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.
class div{
public:
int remainder;
int quotient(int dividend, int divisor){
remainder = ...;
return ...;
}
};
Aquí, estoy escribiendo un programa que devuelve 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.
# include <tuple>
# include <iostream>
using namespace std;
tuple < int,int,int,int,int > cal(int n1, int n2)
{
return make_tuple(n1/n2,n1%n2,n1+n2,n1-n2,n1*n2);
}
int main()
{
int qut,rer,add,sub,mul,a,b;
cin>>a>>b;
tie(qut,rer,add,sub,mul)=cal(a,b);
cout << "quotient= "<<qut<<endl;
cout << "remainder= "<<rer<<endl;
cout << "addition= "<<add<<endl;
cout << "subtraction= "<<sub<<endl;
cout << "multiplication= "<<mul<<endl;
return 0;
}
Entonces, puedes entender claramente que de esta manera puedes devolver múltiples valores de una función. utilizando std :: pair, solo se pueden devolver 2 valores, mientras que std :: tuple puede devolver más de dos valores.
Boost tuple sería mi elección preferida para un sistema generalizado de devolución de más de un valor de una función.
Posible ejemplo:
include "boost/tuple/tuple.hpp"
tuple <int,int> divide( int dividend,int divisor )
{
return make_tuple(dividend / divisor,dividend % divisor )
}
En C ++ 11 puedes:
#include <tuple>
std::tuple<int, int> divide(int dividend, int divisor) {
return std::make_tuple(dividend / divisor, dividend % divisor);
}
#include <iostream>
int main() {
using namespace std;
int quotient, remainder;
tie(quotient, remainder) = divide(14, 3);
cout << quotient << '','' << remainder << endl;
}
En C ++ 17:
#include <tuple>
std::tuple<int, int> divide(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
#include <iostream>
int main() {
using namespace std;
auto [quotient, remainder] = divide(14, 3);
cout << quotient << '','' << remainder << endl;
}
o con estructuras:
#include <tuple>
auto divide(int dividend, int divisor) {
struct result {int quotient; int remainder;};
return result {dividend / divisor, dividend % divisor};
}
#include <iostream>
int main() {
using namespace std;
auto result = divide(14, 3);
cout << result.quotient << '','' << result.remainder << endl;
// or
auto [quotient, remainder] = divide(14, 3);
cout << quotient << '','' << remainder << endl;
}
Es totalmente dependiente de la función real y el significado de los valores múltiples, y sus tamaños:
- Si están relacionados como en tu ejemplo de fracción, entonces iría con una instancia de estructura o clase.
- Si no están realmente relacionados y no pueden agruparse en una clase / estructura, entonces quizás deba refactorizar su método en dos.
- Dependiendo del tamaño en memoria de los valores que está devolviendo, es posible que desee devolver un puntero a una instancia de clase o estructura, o usar parámetros de referencia.
Existe un precedente para las estructuras de retorno en el estándar C (y por lo tanto C ++) con las ldiv
div
, ldiv
(y, en C99, lldiv
) de <stdlib.h>
(o <cstdlib>
).
La ''mezcla de valores de retorno y parámetros de retorno'' suele ser la menos limpia.
Tener una función devolver un estado y devolver datos a través de parámetros de retorno es sensible en C; es menos obvio en C ++, donde podría usar excepciones para retransmitir información de fallas.
Si hay más de dos valores de retorno, entonces probablemente sea mejor un mecanismo similar a una estructura.
La solución de OO para esto es crear una clase de relació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 permiten limpiar el código fuera de esta clase también.
En realidad, creo que alguien recomendó devolver una estructura, que es lo suficientemente parecida pero oculta la intención de que sea una clase totalmente pensada con constructor y algunos métodos, de hecho, el "método" que mencionaste originalmente (como devolver el par) debería ser un miembro de esta clase devolviendo 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 cualquier función debería estar haciendo, si quiere que devuelva múltiples valores, es casi seguro que le falte un objeto.
No tengas miedo de crear estas pequeñas clases para hacer pequeños trabajos - esa es la magia de OO - terminas descomponiéndolo 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 andaba 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 accesorios) es una señal de que es posible que tengas que replantearte algo ...
Las alternativas incluyen matrices, generators 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 para simplificar, 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 es el valor de la función (como es el caso aquí), en lugar de algo que la función toca de manera incidental. 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).
Para devolver dos valores, utilizo un std::pair
(generalmente typedef''d). Debería ver boost::tuple
(en C ++ 11 y más reciente, hay std::tuple
) para más de dos resultados de devolución.
Con la introducción del enlace estructurado en C ++ 17, el regreso de std::tuple
probablemente se convierta en estándar aceptado.
Personalmente, en general no me gustan los parámetros de devolución por varias razones:
- no siempre es obvio en la invocación qué parámetros son ins y cuáles son salidas
- generalmente tiene que crear una variable local para capturar el resultado, mientras que los valores de retorno pueden usarse en línea (lo que puede o no ser una buena idea, pero al menos tiene la opción)
- me parece más limpio tener una "entrada" y una "puerta de salida" a una función: todas las entradas entran aquí, todas las salidas salen
- Me gusta mantener mis listas de argumentos lo más cortas posible
También tengo algunas reservas sobre la técnica de par / tupla. Principalmente, a menudo no hay un orden natural para los valores de retorno. ¿Cómo puede saber el lector del código si el resultado es el primero? ¿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 también se aplican a los parámetros de retorno.
Aquí hay otro ejemplo de código, este es un poco menos trivial:
pair<double,double> calculateResultingVelocity(double windSpeed, double windAzimuth,
double planeAirspeed, double planeCourse);
pair<double,double> result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.first << endl;
cout << result.second << endl;
¿Esto imprime velocidad y curso, o curso y velocidad terrestre? No es obvio
Compare esto:
struct Velocity {
double speed;
double azimuth;
};
Velocity calculateResultingVelocity(double windSpeed, double windAzimuth,
double planeAirspeed, double planeCourse);
Velocity result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.speed << endl;
cout << result.azimuth << endl;
Creo que esto es más claro.
Así que creo que mi primera opción en general es la técnica de estructura. La idea de la pareja / tupla es probablemente una gran solución en ciertos casos. Me gustaría evitar los parámetros de retorno cuando sea posible.
Podemos declarar la función de manera que devuelva una variable definida por el usuario de tipo de 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.)
Si su función devuelve un valor por referencia, el compilador no puede almacenarlo en un registro cuando llama a otras funciones porque, teóricamente, la primera función puede guardar la dirección de la variable que se le pasó en una variable accesible globalmente, y cualquier función subsecuentemente llamada puede cámbielo, de modo que el compilador habrá (1) guardado el valor de los registros en la memoria antes de llamar a otras funciones y (2) lo volverá a leer cuando sea necesario desde la memoria después de cualquiera de esas llamadas.
Si regresas por referencia, la optimización de tu programa sufrirá
Tiendo a usar valores de salida en funciones como esta, porque me atengo al paradigma de una función que devuelve códigos de éxito / error y me gusta mantener las cosas uniformes.
Use una estructura o una clase para el valor de retorno. Usar std::pair
puede funcionar por ahora, pero
- es inflexible si luego decide que desea que se devuelva más información;
- no está muy claro a partir de la declaración de la función en el encabezado qué se devuelve y en qué orden.
La devolución de una estructura con nombres de variables miembro autorregulables probablemente sea menos propensa a errores para cualquiera que use su función. Poniendo mi sombrero de compañero de trabajo por un momento, su estructura de divide_result
es fácil para mí, un usuario potencial de su función, para comprender inmediatamente después de 2 segundos. Perder el tiempo con los parámetros de salida o misteriosos pares y tuplas tomaría más tiempo para leer y se puede utilizar de forma incorrecta. Y lo más probable es que incluso después de usar la función algunas veces, aún no recuerde el orden correcto de los argumentos.
Yo diría que no hay un método preferido, todo depende de lo que vas a hacer con la respuesta. Si los resultados se van a utilizar juntos en un procesamiento posterior, entonces las estructuras tienen sentido, si no, tendería a 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 la copia por encima de devolver una nueva estructura (pero esto está sudando las cosas pequeñas).
void divide(int dividend, int divisor, Answer &ans)
¿Son los parámetros confusos? Un parámetro enviado como referencia sugiere que el valor va a cambiar (a diferencia de una referencia constante). Los nombres sensatos también eliminan la confusión.
en lugar de devolver múltiples valores, simplemente devuelva uno de ellos y haga una referencia de otros en la función requerida, por ejemplo:
int divide(int a,int b,int quo,int &rem)
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 agregados , y lo que se puede llamar constructores con plantilla .
template<typename T1,typename T2,typename T3>
struct many {
T1 a;
T2 b;
T3 c;
};
// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;
auto f(){ return many{string(),5.7, unmovable()}; };
int main(){
// in place construct x,y,z with a string, 5.7 and unmovable.
auto [x,y,z] = f();
}
Lo bonito de esto es que está garantizado que no causará ninguna copia o movimiento. Puedes hacer que el ejemplo sea también muy variable. Más detalles:
std::pair<int, int> divide(int dividend, int divisor)
{
// :
return std::make_pair(quotient, remainder);
}
std::pair<int, int> answer = divide(5,2);
// answer.first == quotient
// answer.second == remainder
std :: pair es esencialmente tu solución de estructura, pero ya definida para ti, y lista para adaptarse a dos tipos de datos.