Rückgabe mehrerer Werte aus einer C ++ – function

Gibt es eine bevorzugte Möglichkeit, mehrere Werte aus einer C ++ – function zurückzugeben? Stellen Sie sich beispielsweise eine function vor, die zwei Ganzzahlen teilt und sowohl den Quotienten als auch den Rest zurückgibt. Eine Möglichkeit, die ich häufig sehe, ist die Verwendung von Referenzparametern:

void divide(int dividend, int divisor, int& quotient, int& remainder); 

Eine Variation besteht darin, einen Wert zurückzugeben und den anderen durch einen Referenzparameter zu übergeben:

 int divide(int dividend, int divisor, int& remainder); 

Eine andere Möglichkeit wäre, eine Struktur so zu deklarieren, dass sie alle Ergebnisse enthält und zurückgibt:

 struct divide_result { int quotient; int remainder; }; divide_result divide(int dividend, int divisor); 

Wird einer dieser Wege im Allgemeinen bevorzugt oder gibt es andere Vorschläge?

Bearbeiten: In dem Code der realen Welt kann mehr als zwei Ergebnisse sein. Sie können auch unterschiedlicher Art sein.

   

Für die Rückgabe von zwei Werten verwende ich ein std::pair -Paar (normalerweise typedef ‘). Sie sollten boost::tuple (in C ++ 11 und neuer, es gibt std::tuple ) für mehr als zwei Return-Ergebnisse betrachten.

Mit der Einführung der strukturierten Bindung in C ++ 17 sollte die Rückkehr von std::tuple wahrscheinlich zum Standard werden.

Ich persönlich mag Rückgabeparameter aus mehreren Gründen im Allgemeinen nicht:

  • Es ist nicht immer offensichtlich im Aufruf, welche Parameter ins sind und welche outs sind
  • Im Allgemeinen müssen Sie eine lokale Variable erstellen, um das Ergebnis abzufangen, während Rückgabewerte inline verwendet werden können (was vielleicht eine gute Idee ist, aber zumindest haben Sie die Option)
  • es scheint mir sauberer zu sein, eine “Tür” und eine “Tür” zu einer function zu haben – alle Eingänge gehen hier rein, alle Ausgänge kommen raus
  • Ich möchte meine Argumentlisten so kurz wie möglich halten

Ich habe auch einige Bedenken bezüglich der Paar / Tupel-Technik. Vor allem gibt es oft keine natürliche Reihenfolge der Rückgabewerte. Wie ist der Leser des Codes zu wissen, ob Ergebnis.erst ist der Quotient oder der Rest? Und der Implementierer könnte die Reihenfolge ändern, wodurch der existierende Code zerstört würde. Dies ist besonders heimtückisch, wenn die Werte vom selben Typ sind, so dass kein Compiler-Fehler oder keine Compiler-Warnung generiert wird. Tatsächlich gelten diese Argumente auch für Rückgabeparameter.

Hier ist ein weiteres Codebeispiel, das ein bisschen weniger trivial ist:

 pair calculateResultingVelocity(double windSpeed, double windAzimuth, double planeAirspeed, double planeCourse); pair result = calculateResultingVelocity(25, 320, 280, 90); cout < < result.first << endl; cout << result.second << endl; 

Druckt dies Grundgeschwindigkeit und Kurs oder Kurs und Grundgeschwindigkeit? Es ist nicht offensichtlich.

Vergleiche dazu:

 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; 

Ich denke, das ist klarer.

Also denke ich, dass meine erste Wahl im Allgemeinen die Struct-Technik ist. Die Paar / Tupel-Idee ist in bestimmten Fällen wahrscheinlich eine großartige Lösung. Ich möchte die Rückgabeparameter wenn möglich vermeiden.

In C ++ 11 können Sie:

 #include  std::tuple divide(int dividend, int divisor) { return std::make_tuple(dividend / divisor, dividend % divisor); } #include  int main() { using namespace std; int quotient, remainder; tie(quotient, remainder) = divide(14, 3); cout < < quotient << ',' << remainder << endl; } 

In C ++ 17:

 #include  std::tuple divide(int dividend, int divisor) { return {dividend / divisor, dividend % divisor}; } #include  int main() { using namespace std; auto [quotient, remainder] = divide(14, 3); cout < < quotient << ',' << remainder << endl; } 

oder mit Strukturen:

 #include  auto divide(int dividend, int divisor) { struct result {int quotient; int remainder;}; return result {dividend / divisor, dividend % divisor}; } #include  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; } 
 std::pair divide(int dividend, int divisor) { // : return std::make_pair(quotient, remainder); } std::pair answer = divide(5,2); // answer.first == quotient // answer.second == remainder 

std :: pair ist im Wesentlichen Ihre struct-Lösung, ist aber bereits für Sie definiert und bereit, sich an zwei beliebige Datentypen anzupassen.

Es hängt vollständig von der tatsächlichen function und der Bedeutung der mehreren Werte und deren Größen ab:

  • Wenn sie wie in Ihrem Bruchbeispiel verwandt sind, würde ich mit einer Struktur oder classninstanz gehen.
  • Wenn sie nicht wirklich verwandt sind und nicht zu einer class / Struktur gruppiert werden können, sollten Sie Ihre Methode vielleicht in zwei Teile umstrukturieren.
  • Abhängig von der Speichergröße der von Ihnen zurückgegebenen Werte möchten Sie möglicherweise einen pointers auf eine classninstanz oder eine Struktur zurückgeben oder Referenzparameter verwenden.

Die OO-Lösung hierfür ist, eine Verhältnisklasse zu erstellen. Es würde keinen zusätzlichen Code benötigen (würde einige sparen), wäre wesentlich sauberer / klarer und würde Ihnen ein paar zusätzliche Refactorings geben, mit denen Sie Code außerhalb dieser class aufräumen können.

Eigentlich denke ich, jemand hat empfohlen, eine Struktur zurückzugeben, die nah genug ist, aber die Absicht verbirgt, dass dies eine vollständig durchdachte class mit Konstruktor und ein paar Methoden sein muss, in der Tat die “Methode”, die du ursprünglich erwähnt hast Paar) sollte höchstwahrscheinlich ein Mitglied dieser class sein und eine Instanz von sich selbst zurückgeben.

Ich weiß, dass Ihr Beispiel nur ein “Beispiel” war, aber Tatsache ist, dass wenn Ihre function viel mehr tut, als irgendeine function es tun sollte, wenn Sie wollen, dass sie mehrere Werte zurückgibt, werden Sie mit Sicherheit ein Objekt vermissen.

Habe keine Angst, diese kleinen classn zu erstellen, um kleine Arbeiten zu machen – das ist die Magie von OO – du brichst sie schließlich auf, bis jede Methode sehr klein und einfach ist und jede class klein und verständlich ist.

Eine andere Sache, die ein Hinweis darauf sein sollte, dass etwas nicht stimmte: In OO gibt es im Wesentlichen keine Daten – OO bedeutet nicht die Weitergabe von Daten, eine class muss ihre eigenen Daten intern verwalten und manipulieren, alle Daten passieren (einschließlich Accessoren) ist ein Zeichen, dass Sie etwas überdenken müssen.

Mit C ++ 17 können Sie auch (in bestimmten Fällen) einen oder mehrere unbewegliche / nicht kopierbare Werte zurückgeben . Die Möglichkeit, unbewegliche Typen zurückzugeben, kommt durch die neue garantierte Rückgabewert-Optimierung zustande und sie fügt sich gut in Aggregate ein , und was man als templated constructors bezeichnen kann .

 template struct many { T1 a; T2 b; T3 c; }; // guide: template many(T1, T2, T3) -> many; 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(); } 

Das Schöne daran ist, dass es garantiert kein Kopieren oder Verschieben verursacht. Sie können das Beispiel auch many struct variadic machen. Mehr Details:

Rückgabe variadischer Aggregate (Struct) und Syntax für C ++ 17 variadische Vorlage ‘Konstruktion Deduktionsleitfaden’

Es gibt Präzedenzfälle für die Rückgabe von Strukturen im C-Standard (und damit C ++) mit den functionen div , ldiv (und in C99, lldiv ) von (oder ).

Die “Mischung aus Rückgabewert und Rückgabeparametern” ist normalerweise am wenigsten sauber.

Das Zurückgeben eines Status und das Zurückgeben von Daten über Rückgabeparameter ist in C sinnvoll; In C ++ ist es weniger offensichtlich sinnvoll, stattdessen Ausnahmen für die Weiterleitung von Fehlerinformationen zu verwenden.

Wenn es mehr als zwei Rückgabewerte gibt, ist wahrscheinlich ein strukturähnlicher Mechanismus am besten.

Verwenden Sie eine Struktur oder eine class für den Rückgabewert. Die Verwendung von std::pair funktioniert jetzt, aber

  1. es ist unflexibel, wenn Sie später entscheiden, dass Sie mehr Informationen zurückgeben möchten;
  2. Es ist nicht sehr klar aus der Deklaration der function in der Kopfzeile, was zurückgegeben wird und in welcher Reihenfolge.

Die Rückgabe einer Struktur mit Variablennamen für selbstdokumentierende Member ist wahrscheinlich weniger errorsanfällig für Benutzer, die Ihre function verwenden. Wenn ich meinen divide_result für einen Moment divide_result , ist es für mich, als potentieller Benutzer Ihrer function, leicht, Ihre divide_result Struktur nach 2 Sekunden sofort zu verstehen. Herumalbern mit Ausgabeparametern oder mysteriösen Paaren und Tupeln würde mehr Zeit zum Durchlesen erfordern und könnte falsch verwendet werden. Und höchstwahrscheinlich werde ich mich auch nach mehrmaliger Verwendung der function immer noch nicht an die richtige Reihenfolge der Argumente erinnern.

Wenn Ihre function einen Wert über einen Verweis zurückgibt, kann der Compiler sie beim Aufrufen anderer functionen nicht in einem Register speichern, da die erste function theoretisch die Adresse der Variablen speichern kann, die ihr in einer global zugänglichen Variablen übergeben wurde, und eventuell nachfolgend aufgerufenen functionen ändere es, so dass der Compiler (1) den Wert aus den Registern in den Speicher zurückspeichern muss, bevor er andere functionen aufruft, und (2) ihn erneut liest, wenn er nach einem solchen Aufruf erneut aus dem Speicher benötigt wird.

Wenn Sie als Referenz zurückkehren, wird die Optimierung Ihres Programms leiden

Hier schreibe ich ein Programm, das mehrere Werte (mehr als zwei Werte) in C ++ zurückgibt. Dieses Programm ist in C ++ 14 (G ++ 4.9.2) ausführbar. Programm ist wie ein Taschenrechner.

 # include  # include  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= "<
		      	

Ich neige dazu, bei functionen wie diesem Out-Vale zu verwenden, weil ich mich an das Paradigma einer function halte, die Erfolgs- / Fehlercodes zurückgibt, und ich möchte die Dinge einheitlich halten.

Alternativen umfassen Arrays, Generatoren und Inversion der Kontrolle , aber keine ist hier angebracht.

Einige (z. B. Microsoft in historischem Win32) tendieren zur Vereinfachung dazu, Referenzparameter zu verwenden, da klar ist, wer allokiert und wie es auf dem Stack aussieht, die Verbreitung von Strukturen reduziert und einen separaten Rückgabewert für den Erfolg ermöglicht.

“Reine” Programmierer bevorzugen die Struktur, wobei angenommen wird , dass es der functionswert ist (wie es hier der Fall ist), anstatt etwas, das zufällig von der function berührt wird. Wenn Sie eine kompliziertere Prozedur oder etwas mit Status hatten, würden Sie wahrscheinlich Referenzen verwenden (vorausgesetzt, Sie haben einen Grund, keine class zu verwenden).

Ich würde sagen, es gibt keine bevorzugte Methode, alles hängt davon ab, was Sie mit der Antwort tun werden. Wenn die Ergebnisse zusammen in der Weiterverarbeitung verwendet werden, dann sind Strukturen sinnvoll, wenn nicht, würde ich dann als einzelne Referenzen bestehen, wenn die function nicht in einer zusammengesetzten statement verwendet würde:

x = divide( x, y, z ) + divide( a, b, c );

Ich entscheide mich oft dafür, ‘Strukturen’ durch Verweis in der Parameterliste zu übergeben, anstatt den Kopieraufwand für die Rückgabe einer neuen Struktur zu übernehmen (aber das schwitzt die kleinen Dinge).

void divide(int dividend, int divisor, Answer &ans)

Sind die Parameter unübersichtlich? Ein als Referenz gesendeter Parameter weist darauf hin, dass sich der Wert ändern wird (im Gegensatz zu einer const-Referenz). Sinnvolle Benennung beseitigt auch Verwirrung.

anstatt mehrere Werte zurückzugeben, geben Sie einfach einen von ihnen zurück und machen Sie einen Verweis auf andere in der erforderlichen function für zB:

 int divide(int a,int b,int quo,int &rem) 

Warum bestehen Sie auf einer function mit mehreren Rückgabewerten? Mit OOP können Sie eine class verwenden, die eine reguläre function mit einem einzelnen Rückgabewert und eine beliebige Anzahl zusätzlicher “Rückgabewerte” wie unten bietet. Der Vorteil besteht darin, dass der Anrufer die Möglichkeit hat, die zusätzlichen Datenelemente zu betrachten, dies aber nicht benötigt. Dies ist die bevorzugte Methode für komplizierte database- oder Netzwerkanrufe, bei denen viele zusätzliche Rücksende-Informationen erforderlich sind, falls Fehler auftreten.

Um Ihre ursprüngliche Frage zu beantworten, enthält dieses Beispiel eine Methode zum Zurückgeben des Quotienten, die von den meisten Aufrufern möglicherweise benötigt wird. Außerdem können Sie nach dem Methodenaufruf den Rest als Datenelement abrufen.

 class div{ public: int remainder; int quotient(int dividend, int divisor){ remainder = ...; return ...; } }; 

Boost-Tupel wäre meine bevorzugte Wahl für ein verallgemeinertes System, bei dem mehr als ein Wert von einer function zurückgegeben wird.

Mögliches Beispiel:

 include "boost/tuple/tuple.hpp" tuple  divide( int dividend,int divisor ) { return make_tuple(dividend / divisor,dividend % divisor ) } 

Wir können die function so deklarieren, dass sie eine benutzerdefinierte Strukturvariable oder einen pointers darauf zurückgibt. Und durch die Eigenschaft einer Struktur wissen wir, dass eine Struktur in C mehrere Werte asymmetrischer Typen enthalten kann (dh eine int-Variable, vier char-Variablen, zwei float-Variablen usw.).