Rückgabe mehrerer Werte aus einer C ++ - Funktion

242

Gibt es eine bevorzugte Möglichkeit, mehrere Werte von einer C ++ - Funktion zurückzugeben? Stellen Sie sich zum Beispiel eine Funktion vor, die zwei ganze Zahlen 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 über einen Referenzparameter zu übergeben:

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

Eine andere Möglichkeit wäre, eine Struktur zu deklarieren, die alle Ergebnisse enthält, und Folgendes zurückzugeben:

struct divide_result {
    int quotient;
    int remainder;
};

divide_result divide(int dividend, int divisor);

Wird eine dieser Möglichkeiten allgemein bevorzugt oder gibt es andere Vorschläge?

Bearbeiten: Im realen Code kann es mehr als zwei Ergebnisse geben. Sie können auch von verschiedenen Typen sein.

Fred Larson
quelle

Antworten:

216

Für die Rückgabe von zwei Werten verwende ich a std::pair(normalerweise typedef'd). Sie sollten sich boost::tuple(in C ++ 11 und std::tuplehöher gibt es ) mehr als zwei Rückgabeergebnisse ansehen .

Mit der Einführung der strukturierten Bindung in C ++ 17 sollte die Rückgabe std::tuplewahrscheinlich zum akzeptierten Standard werden.

rauben
quelle
12
+1 für Tupel. Beachten Sie die Leistungsauswirkungen großer Objekte, die in einer Struktur zurückkehren, im Vergleich zur Referenzübergabe.
Marcin
12
Wenn Sie Tupel verwenden möchten, können Sie sie auch für Paare verwenden. Warum einen Sonderfall?
Ferruccio
4
Fred, ja boost :: tuple kann das :)
Johannes Schaub - litb
46
In C ++ 11 können Sie verwenden std::tuple.
Ferruccio
14
Wenn Sie mehrere Werte von einer Funktion akzeptieren möchten, können Sie dies bequem über std::tie stackoverflow.com/a/2573822/502144
fdermishin
175

In C ++ 11 können Sie:

#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;
}

In 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;
}

oder mit Strukturen:

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;
}
Pfeffer_chico
quelle
4
Ich habe ein Problem mit Funktionen, die Tupel zurückgeben. Angenommen, der obige Funktionsprototyp befindet sich in einem Header. Woher weiß ich dann, was der erste und der zweite zurückgegebene Wert bedeuten, ohne die Funktionsdefinition zu verstehen? Quotientenrest oder Restquotient.
Uchia Itachi
7
@UchiaItachi Gleiches Problem mit Funktionsparametern, Sie können ihnen Namen geben, aber die Sprache erzwingt dies nicht einmal, und die Parameternamen haben beim Lesen an der Aufrufstelle keinen Wert. Bei einer einzelnen Rückgabe haben Sie nur einen Typ, aber der Name könnte auch nützlich sein. Mit Tupeln verdoppeln Sie das Problem. Imo fehlt der Sprache nur die Möglichkeit, sich auf verschiedene Weise selbst zu dokumentieren, nicht nur auf diese Weise.
Pepper_chico
1
Wie würde das letzte Beispiel aussehen, wenn ich den Rückgabetyp von divid () explizit angeben wollte? Soll ich das Ergebnis dann woanders definieren oder kann ich es direkt in der Rückgabetypspezifikation definieren?
Slava
1
@Slava Sie können einen Typ nicht direkt bei der Funktionssignatur definieren. Sie müssten den Typ außerhalb deklarieren und ihn wie gewohnt als Rückgabetyp verwenden (verschieben Sie einfach die structZeile außerhalb des Funktionskörpers und ersetzen Sie die autoFunktionsrückgabe durch result.
Pepper_chico
3
@pepper_chico Was ist, wenn Sie die Funktionsdefinition von dividein eine separate CPP-Datei einfügen möchten ? Ich bekomme den Fehler error: use of ‘auto divide(int, int)’ before deduction of ‘auto’. Wie löse ich das?
Adriaan
123

Persönlich mag ich Rückgabeparameter aus einer Reihe von Gründen im Allgemeinen nicht:

  • Beim Aufruf ist nicht immer ersichtlich, welche Parameter ins und welche out sind
  • Im Allgemeinen müssen Sie eine lokale Variable erstellen, um das Ergebnis abzufangen, während Rückgabewerte inline verwendet werden können (was eine gute Idee sein kann oder nicht, aber zumindest haben Sie die Option).
  • Es scheint mir sauberer, eine "In-Tür" und eine "Out-Tür" für eine Funktion zu haben - alle Eingänge gehen hier rein, alle Ausgänge kommen dort raus
  • Ich möchte meine Argumentationslisten so kurz wie möglich halten

Ich habe auch einige Vorbehalte gegen die Paar / Tupel-Technik. Hauptsächlich gibt es oft keine natürliche Reihenfolge für die Rückgabewerte. Wie kann der Leser des Codes wissen, ob result.first der Quotient oder der Rest ist? Und der Implementierer könnte die Reihenfolge ändern, wodurch der vorhandene Code beschädigt würde. Dies ist besonders heimtückisch, wenn die Werte vom gleichen Typ sind, sodass kein Compilerfehler oder keine Warnung generiert wird. Tatsächlich gelten diese Argumente auch für Rückgabeparameter.

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

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;

Gibt dies Grundgeschwindigkeit und Kurs oder Kurs und Grundgeschwindigkeit aus? Es ist nicht offensichtlich.

Vergleichen Sie damit:

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.

Daher denke ich, dass meine erste Wahl im Allgemeinen die Strukturtechnik ist. Die Paar / Tupel-Idee ist in bestimmten Fällen wahrscheinlich eine großartige Lösung. Ich möchte die Rückgabeparameter nach Möglichkeit vermeiden.

Fred Larson
quelle
1
Der Vorschlag, ein structLike zu deklarieren, Velocityist nett. Eine Sorge ist jedoch, dass es den Namespace verschmutzt. Ich nehme an, dass mit C ++ 11 die structeinen langen Typnamen haben können und man verwenden kann auto result = calculateResultingVelocity(...).
Hugues
5
+1. Eine Funktion sollte eine "Sache" zurückgeben, kein irgendwie geordnetes "Tupel der Dinge".
DevSolar
1
Aus den in dieser Antwort beschriebenen Gründen bevorzuge ich Strukturen gegenüber std :: pair / std :: tuples. Aber ich mag den Namespace "Verschmutzung" auch nicht. Die ideale Lösung für mich wäre die Rückgabe einer anonymen Struktur wie struct { int a, b; } my_func();. Dies könnte wie folgt verwendet werden : auto result = my_func();. C ++ erlaubt dies jedoch nicht: "Neue Typen dürfen in einem Rückgabetyp nicht definiert werden". Also muss ich Strukturen erstellen wie struct my_func_result_t...
anton_rh
2
@anton_rh: C ++ 14 ermöglicht die Rückgabe lokaler Typen mit auto, auto result = my_func();ist also trivial erhältlich.
ildjarn
4
Vor ungefähr 15 Jahren, als wir Boost entdeckten, haben wir viel Tupel verwendet, da es sehr praktisch ist. Im Laufe der Zeit hatten wir den Nachteil der Lesbarkeit insbesondere bei Tupeln des gleichen Typs (z. B. Tupel <double, double>; welches ist welches). In letzter Zeit haben wir eher die Gewohnheit, eine kleine POD-Struktur einzuführen, bei der zumindest der Name der Mitgliedsvariablen auf etwas Sinnvolles hinweist.
Gast128
24
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 ist im Wesentlichen Ihre Strukturlösung, aber bereits für Sie definiert und bereit, sich an zwei beliebige Datentypen anzupassen.

James Curran
quelle
3
Das wird für mein einfaches Beispiel funktionieren. Im Allgemeinen können jedoch mehr als zwei Werte zurückgegeben werden.
Fred Larson
5
Auch nicht selbstdokumentierend. Können Sie sich erinnern, welches x86-Register der Rest für DIV ist?
Mark
1
@Mark - Ich stimme zu, dass Positionslösungen weniger wartbar sein können. Sie können auf das Problem "Permute and Baffle" stoßen.
Fred Larson
16

Es hängt ganz von der tatsächlichen Funktion und der Bedeutung der mehreren Werte und ihrer Größe ab:

  • Wenn sie wie in Ihrem Bruchbeispiel verwandt sind, würde ich mich für eine Struktur- oder Klasseninstanz entscheiden.
  • Wenn sie nicht wirklich verwandt sind und nicht in eine Klasse / Struktur gruppiert werden können, sollten Sie Ihre Methode möglicherweise in zwei Teile umgestalten.
  • Abhängig von der speicherinternen Größe der zurückgegebenen Werte möchten Sie möglicherweise einen Zeiger auf eine Klasseninstanz oder -struktur zurückgeben oder Referenzparameter verwenden.
Stewart Johnson
quelle
1
Ich mag Ihre Antwort und Ihre letzte Kugel erinnert mich an etwas, das ich gerade gelesen habe, dass das Übergeben von Werten je nach den Umständen, die dies komplizierter machen, viel schneller geworden ist ... cpp-next.com/archive/2009/08/want-speed-pass -by-value
Salbei
12

Die OO-Lösung hierfür besteht darin, eine Verhältnisklasse zu erstellen. Es würde keinen zusätzlichen Code erfordern (würde etwas sparen), wäre wesentlich sauberer / klarer und würde Ihnen einige zusätzliche Refactorings geben, mit denen Sie Code auch außerhalb dieser Klasse bereinigen können.

Eigentlich denke ich, dass jemand empfohlen hat, eine Struktur zurückzugeben, die nah genug ist, aber die Absicht verbirgt, dass dies eine vollständig durchdachte Klasse mit Konstruktor und einigen Methoden sein muss, in der Tat die "Methode", die Sie ursprünglich erwähnt haben (als Rückgabe der Paar) sollte höchstwahrscheinlich ein Mitglied dieser Klasse sein, das eine Instanz von sich selbst zurückgibt.

Ich weiß, dass Ihr Beispiel nur ein "Beispiel" war, aber Tatsache ist, dass Sie mit ziemlicher Sicherheit ein Objekt vermissen, wenn Ihre Funktion nicht mehr als jede andere Funktion ausführen soll, wenn Sie möchten, dass sie mehrere Werte zurückgibt.

Haben Sie keine Angst, diese winzigen Klassen zu erstellen, um kleine Arbeiten zu erledigen - das ist die Magie von OO - Sie brechen sie am Ende auf, bis jede Methode sehr klein und einfach und jede Klasse klein und verständlich ist.

Eine andere Sache, die ein Indikator dafür sein sollte, dass etwas nicht stimmte: In OO haben Sie im Wesentlichen keine Daten - bei OO geht es nicht darum, Daten weiterzugeben, eine Klasse muss ihre eigenen Daten intern verwalten und bearbeiten, alle Daten, die weitergegeben werden (einschließlich Accessoren). ist ein Zeichen dafür, dass Sie möglicherweise etwas überdenken müssen ..

Bill K.
quelle
10

Es ist Präzedenzfall für Strukturen in der C (und damit die C ++) -Standard mit der zurückkehrende div, ldiv(und in C99, lldiv) Funktionen aus <stdlib.h>(oder <cstdlib>).

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

Es ist in C sinnvoll, wenn eine Funktion einen Status zurückgibt und Daten über Rückgabeparameter zurückgibt. In C ++ ist dies weniger sinnvoll, da Sie stattdessen Ausnahmen verwenden können, um Fehlerinformationen weiterzuleiten.

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

Jonathan Leffler
quelle
10

Mit C ++ 17 können Sie auch einen oder mehrere nicht verschiebbare / nicht kopierbare Werte zurückgeben (in bestimmten Fällen). Die Möglichkeit, nicht verschiebbare Typen zurückzugeben, ergibt sich aus der neuen Optimierung des garantierten Rückgabewerts, die sich gut aus Aggregaten und so genannten Vorlagenkonstruktoren zusammensetzt .

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();
}

Das Schöne daran ist, dass es garantiert kein Kopieren oder Verschieben verursacht. Sie können die Beispielstruktur auch manyvariadic machen. Mehr Details:

Rückgabe von variadischen Aggregaten (struct) und Syntax für die varadische C ++ 17-Vorlage 'Konstruktionsabzugshandbuch'

Johan Lundberg
quelle
6

Es gibt verschiedene Möglichkeiten, mehrere Parameter zurückzugeben. Ich werde exhastiv sein.

Referenzparameter verwenden:

void foo( int& result, int& other_result );

Zeigerparameter verwenden:

void foo( int* result, int* other_result );

Dies hat den Vorteil, dass Sie &an der Anrufstelle eine Aktion durchführen müssen, um möglicherweise die Leute darauf aufmerksam zu machen, dass es sich um einen Out-Parameter handelt.

Schreiben Sie eine Vorlage und verwenden Sie sie:

template<class T>
struct out {
  std::function<void(T)> target;
  out(T* t):target([t](T&& in){ if (t) *t = std::move(in); }) {}
  out(std::optional<T>* t):target([t](T&& in){ if (t) t->emplace(std::move(in)); }) {}
  out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
    target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
  template<class...Args> // TODO: SFINAE enable_if test
  void emplace(Args&&...args) {
    target( T(std::forward<Args>(args)...) );
  }
  template<class X> // TODO: SFINAE enable_if test
  void operator=(X&&x){ emplace(std::forward<X>(x)); }
  template<class...Args> // TODO: SFINAE enable_if test
  void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};

dann können wir tun:

void foo( out<int> result, out<int> other_result )

und alles ist gut. fookann keinen als Bonus übergebenen Wert mehr lesen.

Zum Erstellen können Sie auch andere Möglichkeiten zum Definieren eines Spots verwenden, an dem Sie Daten ablegen können out. Ein Rückruf zum Beispiel, um Dinge irgendwo zu platzieren.

Wir können eine Struktur zurückgeben:

struct foo_r { int result; int other_result; };
foo_r foo();

whick funktioniert in jeder Version von C ++ und in dies erlaubt auch:

auto&&[result, other_result]=foo();

zu null Kosten. Parameter können dank garantierter Elision nicht einmal verschoben werden.

Wir könnten ein std::tuple:

std::tuple<int, int> foo();

Das hat den Nachteil, dass Parameter nicht benannt werden. Dies ermöglicht die::

auto&&[result, other_result]=foo();

auch. Vor wir können stattdessen tun:

int result, other_result;
std::tie(result, other_result) = foo();

das ist nur ein bisschen umständlicher. Garantierte Elision funktioniert hier jedoch nicht.

Wenn out<>wir fremdes Gebiet betreten (und das ist danach !), Können wir den Continuation-Passing-Stil verwenden:

void foo( std::function<void(int result, int other_result)> );

und jetzt tun Anrufer:

foo( [&](int result, int other_result) {
  /* code */
} );

Ein Vorteil dieses Stils besteht darin, dass Sie eine beliebige Anzahl von Werten (mit einheitlichem Typ) zurückgeben können, ohne den Speicher verwalten zu müssen:

void get_all_values( std::function<void(int)> value )

Der valueRückruf kann bei Ihnen 500 Mal aufgerufen werden get_all_values( [&](int value){} ).

Für reinen Wahnsinn könnten Sie sogar eine Fortsetzung der Fortsetzung verwenden.

void foo( std::function<void(int, std::function<void(int)>)> result );

deren Verwendung sieht aus wie:

foo( [&](int result, auto&& other){ other([&](int other){
  /* code */
}) });

das würde viele-eins-Beziehungen zwischen resultund ermöglichen other.

Wieder mit unifornischen Werten können wir dies tun:

void foo( std::function< void(span<int>) > results )

Hier nennen wir den Rückruf mit einer Reihe von Ergebnissen. Wir können dies sogar wiederholt tun.

Mit dieser Funktion können Sie eine Funktion verwenden, die Megabyte an Daten effizient weiterleitet, ohne eine Zuordnung vom Stapel vorzunehmen.

void foo( std::function< void(span<int>) > results ) {
  int local_buffer[1024];
  std::size_t used = 0;
  auto send_data=[&]{
    if (!used) return;
    results({ local_buffer, used });
    used = 0;
  };
  auto add_datum=[&](int x){
    local_buffer[used] = x;
    ++used;
    if (used == 1024) send_data();
  };
  auto add_data=[&](gsl::span<int const> xs) {
    for (auto x:xs) add_datum(x);
  };
  for (int i = 0; i < 7+(1<<20); ++i) {
    add_datum(i);
  }
  send_data(); // any leftover
}

std::functionDies ist jetzt etwas schwierig, da wir dies in Umgebungen ohne Zuweisung ohne Overhead tun würden. Wir möchten also eine function_view, die niemals zuweist.

Eine andere Lösung ist:

std::function<void(std::function<void(int result, int other_result)>)> foo(int input);

Anstatt den Rückruf anzunehmen und aufzurufen, wird foostattdessen eine Funktion zurückgegeben, die den Rückruf entgegennimmt.

foo (7) ([&] (int result, int other_result) {/ * code * /}); Dadurch werden die Ausgabeparameter von den Eingabeparametern getrennt, indem separate Klammern verwendet werden.

Mit variantundCoroutinen, Sie könnten fooeinen Generator aus einer Variante der Rückgabetypen (oder nur dem Rückgabetyp) erstellen. Die Syntax ist noch nicht festgelegt, daher werde ich keine Beispiele nennen.

In der Welt der Signale und Slots eine Funktion, die eine Reihe von Signalen verfügbar macht:

template<class...Args>
struct broadcaster;

broadcaster<int, int> foo();

Mit foodieser Option können Sie eine erstellen , die asynchron funktioniert und das Ergebnis nach Abschluss sendet.

In dieser Richtung gibt es eine Vielzahl von Pipeline-Techniken, bei denen eine Funktion nichts tut, sondern dafür sorgt, dass Daten auf irgendeine Weise verbunden werden, und das Vorgehen ist relativ unabhängig.

foo( int_source )( int_dest1, int_dest2 );

dann ist dieser Code nicht tut nichts , bis int_sourceganze Zahlen , es zu bieten hat. Wenn es der Fall ist, int_dest1und int_dest2starten Sie die Ergebnisse recieving.

Yakk - Adam Nevraumont
quelle
Diese Antwort enthält mehr Informationen als andere Antworten! insbesondere Informationen über auto&&[result, other_result]=foo();Funktionen, die sowohl Tupel als auch Strukturen zurückgeben. Vielen Dank!
Jjmontes
Ich schätze diese erschöpfende Antwort, zumal ich immer noch mit C ++ 11 beschäftigt bin und daher einige der moderneren Lösungen, die andere Leute vorschlagen, nicht verwenden kann.
GuyGizmo
5

Verwenden Sie eine Struktur oder eine Klasse für den Rückgabewert. Die Verwendung std::pairmag vorerst funktionieren, aber

  1. Es ist unflexibel, wenn Sie später entscheiden, dass weitere Informationen zurückgegeben werden sollen.
  2. Aus der Deklaration der Funktion im Header geht nicht klar hervor, was in welcher Reihenfolge zurückgegeben wird.

Das Zurückgeben einer Struktur mit selbstdokumentierenden Mitgliedsvariablennamen ist wahrscheinlich weniger fehleranfällig für alle, die Ihre Funktion verwenden. Wenn Sie für einen Moment meinen Kollegenhut aufsetzen, ist Ihre divide_resultStruktur für mich, einen potenziellen Benutzer Ihrer Funktion, nach 2 Sekunden sofort verständlich. Das Herumspielen mit Ausgabeparametern oder mysteriösen Paaren und Tupeln würde mehr Zeit zum Lesen benötigen und möglicherweise falsch verwendet werden. Und höchstwahrscheinlich kann ich mich auch nach mehrmaliger Verwendung der Funktion nicht an die richtige Reihenfolge der Argumente erinnern.

Michel
quelle
4

Wenn Ihre Funktion einen Wert über eine Referenz zurückgibt, kann der Compiler ihn beim Aufrufen anderer Funktionen nicht in einem Register speichern, da die erste Funktion theoretisch die Adresse der ihr übergebenen Variablen in einer global zugänglichen Variablen speichern kann und alle nachfolgend aufgerufenen Funktionen dies können Ändern Sie es, damit der Compiler (1) den Wert aus den Registern zurück in den Speicher speichern muss, bevor er andere Funktionen 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, leidet die Optimierung Ihres Programms

dmityugov
quelle
4

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 <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;
}

Sie können also klar verstehen, dass Sie auf diese Weise mehrere Werte von einer Funktion zurückgeben können. Mit std :: pair können nur 2 Werte zurückgegeben werden, während std :: tuple mehr als zwei Werte zurückgeben kann.

PRAFUL ANAND
quelle
4
Mit C ++ 14 können Sie auch den autoRückgabetyp aktivieren cal, um dies noch sauberer zu machen. (IMO).
Sfjac
3

Ich neige dazu, Out-Vals in Funktionen wie diesen zu verwenden, weil ich mich an das Paradigma einer Funktion halte, die Erfolgs- / Fehlercodes zurückgibt, und ich mag es, die Dinge einheitlich zu halten.

John Dibling
quelle
2

Alternativen umfassen Arrays, Generatoren und Inversion der Steuerung , aber keine ist hier angemessen.

Einige (z. B. Microsoft im historischen Win32) verwenden der Einfachheit halber Referenzparameter, da klar ist, wer auf dem Stapel zugeordnet ist und wie es aussehen wird, die Verbreitung von Strukturen verringert und einen separaten Rückgabewert für den Erfolg ermöglicht.

"Reine" Programmierer bevorzugen die Struktur, vorausgesetzt, es ist der Funktionswert (wie hier der Fall), anstatt etwas, das zufällig von der Funktion berührt wird. Wenn Sie eine kompliziertere Prozedur oder etwas mit Status hätten, würden Sie wahrscheinlich Referenzen verwenden (vorausgesetzt, Sie haben einen Grund, keine Klasse zu verwenden).

Kennzeichen
quelle
2

Ich würde sagen, es gibt keine bevorzugte Methode, alles hängt davon ab, was Sie mit der Antwort machen werden. Wenn die Ergebnisse bei der weiteren Verarbeitung zusammen verwendet werden sollen, sind Strukturen sinnvoll. Wenn nicht, würde ich sie eher als einzelne Referenzen übergeben, es sei denn, die Funktion würde in einer zusammengesetzten Anweisung verwendet:

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

Ich entscheide mich oft dafür, 'Strukturen' als Referenz in der Parameterliste auszugeben, anstatt den Aufwand für das Übergeben einer Kopie durch das Zurückgeben einer neuen Struktur zu haben (aber das schwitzt die kleinen Dinge).

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

Sind unsere Parameter verwirrend? Ein als Referenz gesendeter Parameter deutet darauf hin, dass sich der Wert ändern wird (im Gegensatz zu einer konstanten Referenz). Eine vernünftige Benennung beseitigt auch Verwirrung.

Patrick
quelle
1
Ich finde es etwas verwirrend. Jemand, der Code liest, der ihn aufruft, sieht "dividieren (a, b, c);". Es gibt keinen Hinweis darauf, dass c ein Outval ist, bis sie die Signatur nachschlagen. Aber das ist eher eine allgemeine Angst vor nicht konstanten Referenzparametern als eine besondere für diese Frage.
Steve Jessop
2

Warum bestehen Sie auf einer Funktion mit mehreren Rückgabewerten? Mit OOP können Sie eine Klasse verwenden, die eine reguläre Funktion mit einem einzelnen Rückgabewert und einer beliebigen Anzahl zusätzlicher "Rückgabewerte" wie unten bietet. Der Vorteil besteht darin, dass der Anrufer die Möglichkeit hat, sich die zusätzlichen Datenelemente anzusehen, dies jedoch nicht tun muss. Dies ist die bevorzugte Methode für komplizierte Datenbank- oder Netzwerkanrufe, bei denen im Fehlerfall viele zusätzliche Rückgabeinformationen erforderlich sein können.

Um Ihre ursprüngliche Frage zu beantworten, enthält dieses Beispiel eine Methode zum Zurückgeben des Quotienten, die die meisten Anrufer möglicherweise benötigen. 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 ...;
      }
};
Roland
quelle
1
Ich denke, es gibt Fälle, in denen dies ineffizient ist. Sie haben beispielsweise eine einzelne for-Schleife, die mehrere Rückgabewerte generiert. Wenn Sie diese Werte in separate Funktionen aufteilen, müssen Sie die Schleife für jeden Wert einmal durchlaufen.
Jiggunjer
1
@jiggunjer Sie können die Schleife einmal ausführen und die verschiedenen Rückgabewerte in separaten Klassendatenelementen speichern. Dies unterstreicht die Flexibilität des OOP-Konzepts.
Roland
2

Anstatt mehrere Werte zurückzugeben, geben Sie einfach einen von ihnen zurück und verweisen Sie in der erforderlichen Funktion auf andere, z.

int divide(int a,int b,int quo,int &rem)
Anchit Rana
quelle
Habe ich das nicht in der Frage selbst erwähnt? Siehe auch meine Einwände in meiner Antwort .
Fred Larson
1

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

Mögliches Beispiel:

include "boost/tuple/tuple.hpp"

tuple <int,int> divide( int dividend,int divisor ) 

{
  return make_tuple(dividend / divisor,dividend % divisor )
}
AndyUK
quelle
1

Wir können die Funktion so deklarieren, dass sie eine benutzerdefinierte Variable vom Strukturtyp oder einen Zeiger 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.)

Rohit Hajare
quelle
1

Ich würde es nur als Referenz tun, wenn es nur ein paar Rückgabewerte sind, aber für komplexere Typen können Sie es auch einfach so machen:

static struct SomeReturnType {int a,b,c; string str;} SomeFunction()
{
  return {1,2,3,string("hello world")}; // make sure you return values in the right order!
}

Verwenden Sie "static", um den Umfang des Rückgabetyps auf diese Kompilierungseinheit zu beschränken, wenn es sich nur um einen temporären Rückgabetyp handelt.

 SomeReturnType st = SomeFunction();
 cout << "a "   << st.a << endl;
 cout << "b "   << st.b << endl;
 cout << "c "   << st.c << endl;
 cout << "str " << st.str << endl;

Dies ist definitiv nicht der schönste Weg, aber es wird funktionieren.

Carsten
quelle
-2

Hier ist ein vollständiges Beispiel für eine solche Problemlösung

#include <bits/stdc++.h>
using namespace std;
pair<int,int> solve(int brr[],int n)
{
    sort(brr,brr+n);

    return {brr[0],brr[n-1]};
}

int main()
{
    int n;
    cin >> n;
    int arr[n];
    for(int i=0; i<n; i++)
    {
        cin >> arr[i];
    }

    pair<int,int> o=solve(arr,n);
    cout << o.first << " " << o.second << endl;

    return 0;
}
Shaonsani
quelle