Frage

Ich versuche, meinen Kopf um Tupeln (dank @litb) und der gemeinsame Vorschlag für ihre Verwendung zu erhalten, ist für Funktionen Rückkehr> 1 Wertes.

Das ist etwas, das ich normalerweise eine Struktur für verwenden würde, und ich kann die Vorteile Tupeln in diesem Fall nicht verstehen -. Es scheint einen fehleranfällige Ansatz für die unheilbar faul

ein Beispiel Leihen, würde ich diese verwenden

struct divide_result {
    int quotient;
    int remainder;
};

ein Tupel verwenden, müssten Sie

typedef boost::tuple<int, int> divide_result;

Aber ohne den Code der Funktion Lesen Sie anrufen (oder die Kommentare, wenn Sie dumm genug sind, ihnen zu vertrauen) Sie keine Ahnung haben, die int ist Quotient und umgekehrt. Es scheint eher wie ...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

... was mich nicht füllen würde mit Zuversicht entgegen.

Also, was sind die Vorteile von Tupeln über structs, die für die Mehrdeutigkeit kompensieren?

War es hilfreich?

Lösung

Tupel

Ich glaube, ich stimme mit Ihnen überein, dass die Frage mit welcher Position in welcher Größe entspricht Verwirrung vorstellen kann. Aber ich denke, es gibt zwei Seiten. Eine davon ist die call-Seite und die andere ist die Angerufenen-Seite :

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

Ich denke, es ist kristallklar, was wir haben, aber es kann verwirrend sein, wenn Sie mehrere Werte zurückgeben sofort müssen. Sobald der Programmierer des Anrufers in die Dokumentation von div aufblickte, wird er wissen, welche Position, was ist, und effektiven Code schreiben kann. Als Faustregel würde ich sagen, nicht mehr als 4 Werte auf einmal zurückzukehren. Für alles, was darüber hinaus bevorzugt eine Struktur.

Ausgabeparameter

Output-Parameter können auch verwendet werden, natürlich:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

Jetzt denke ich, dass zeigt, wie Tupeln besser ist als Ausgangsparameter. Wir haben den Eingang des div mit seinem Ausgang gemischt, während keinen Vorteil zu gewinnen. Schlimmer noch, verlassen wir die Leser dieses Codes im Zweifel auf, was das sein könnte eigentlichen Rückgabewert von div sein. Es sind wunderbare Beispiele, wenn die Ausgangsparameter sind nützlich. Meiner Meinung nach, sollten Sie sie nur verwenden, wenn Sie keine andere Möglichkeit haben, weil der Rückgabewert bereits belegt ist und nicht entweder ein Tupel oder Struktur verändert werden kann. operator>> ist ein gutes Beispiel, wo Sie Ausgangsparameter verwenden, da der Rückgabewert bereits für den Stream reserviert ist, so können Sie Kette operator>> Anrufe. Wenn Sie nicht mit den Betreibern zu tun haben, und der Kontext ist nicht kristallklar, empfehle ich Ihnen, Zeiger zu verwenden, auf der Anrufseite zu signalisieren, dass das Objekt tatsächlich als Ausgabeparameter, zusätzlich zu den Kommentaren verwendet wird, wo angemessen.

Zurückgeben eine Struktur

Die dritte Option ist eine Struktur zu verwenden:

div_result d = div(10, 3);

Ich denke, dass auf jeden Fall den Preis gewinnt für Klarheit . Aber beachten Sie haben noch innerhalb dieser Struktur das Ergebnis für den Zugriff auf und das Ergebnis „entblößte“ nicht auf dem Tisch, wie es der Fall für die Ausgangsparameter war und das Tupel verwendet mit tie.

Ich denke, ein wichtiger Punkt in diesen Tagen ist alles so allgemein wie möglich zu machen. Also, sagen Sie eine Funktion haben, die Tupel ausdrucken können. Sie können nur das tun,

cout << div(10, 3);

Und hat Ihr Ergebnis angezeigt. Ich denke, dass Tupel, auf der anderen Seite, deutlich gewinnen für ihre vielseitig Natur. Dadurch, dass mit div_result, müssen Sie Betreiber zu überlasten <<, oder müssen jedes Mitglied einzeln ausgegeben.

Andere Tipps

Eine weitere Option ist eine Boost Fusion Karte (Code ungetestet) zu verwenden:

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

Sie können die Ergebnisse zugreifen relativ intuitiv:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

Es gibt noch andere Vorteile zu, wie die Fähigkeit zu iterate über die Felder der Karte, etc etc. Siehe doco für weitere Informationen.

Mit Tupeln können Sie tie verwenden, die manchmal ganz nützlich ist: std::tr1::tie (quotient, remainder) = do_division ();. Das ist nicht so einfach, mit structs. Zweitens, wenn Template-Code verwenden, ist es manchmal einfacher, auf Paare zu verlassen, als noch eine weitere typedef für den Strukturtyp hinzuzufügen.

Und wenn die Typen unterschiedlich sind, dann ein Paar / Tupel ist wirklich nicht schlechter als eine Struktur. Denken Sie zum Beispiel pair<int, bool> readFromFile(), wo die int ist die Anzahl der gelesenen Bytes und Bool ist, ob die EOF getroffen wurde. Hinzufügen einer Struktur in diesem Fall scheint übertrieben für mich, vor allem, da es keine Zweideutigkeit hier.

Tupeln sind sehr nützlich, in Sprachen wie ML oder Haskell.

In C ++, deren Syntax macht sie weniger elegant, aber kann in den folgenden Situationen nützlich sein:

  • Sie haben eine Funktion, die mehr als ein Argument zurückgeben muss, aber das Ergebnis ist „local“ zu dem Anrufer und dem Angerufenen; Sie wollen nicht eine Struktur nur für diesen

  • definieren,
  • Sie können die Krawatte Funktion verwenden, um eine sehr begrenzte Form von Pattern-Matching "a la ML" zu tun, die mehr elegant ist es, eine Struktur für den gleichen Zweck als verwenden.

  • Sie kommen mit vordefinierten

Ich neige dazu, Tupeln in Verbindung mit typedefs zumindest teilweise zu lindern das ‚namenlos tuple‘ Problem zu verwenden. Zum Beispiel, wenn ich habe eine Gitterstruktur dann:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

Dann benutze ich die genannte Art wie:

grid_index find(const grid& g, int value);

Dies ist ein etwas konstruiertes Beispiel, aber ich denke, die meiste Zeit trifft es einen Mittelweg zwischen Lesbarkeit, Ausführlichkeit und einfacher Bedienung.

oder in Ihrem Beispiel:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);

Ein Merkmal von Tupeln, die Sie mit structs nicht haben, ist in ihrer Initialisierung. Betrachten wir etwa wie folgt:

struct A
{
  int a;
  int b;
};

Wenn Sie ein make_tuple äquivalent oder Konstruktor schreiben dann diese Struktur als Eingabeparameter verwenden Sie zunächst ein temporäres Objekt erstellen haben:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

Nicht schlecht, aber nehmen wir den Fall, daß keine Wartung aus irgendeinem Grund ein neues Mitglied in unserer Struktur fügt hinzu:

struct A
{
  int a;
  int b;
  int c;
};

Die Regeln der aggregierten Initialisierung tatsächlich bedeuten, dass unser Code ohne Änderung kompilieren wird fortgesetzt. Wir haben daher für alle Verwendungen dieser Struktur zu suchen und aktualisieren sie, ohne Hilfe vom Compiler.

Kontrast dies mit einem Tupel:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

Der Compiler kann nicht „Tuple“ mit dem Ergebnis der make_tuple initailize und erzeugt so den Fehler, dass Sie die richtigen Werte für den dritten Parameter angeben kann.

Schließlich ist der andere Vorteil von Tupeln, dass sie ermöglichen es Ihnen, Code zu schreiben, die jeden Wert iteriert. Das ist einfach nicht möglich, eine Struktur verwenden.

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}

Verhindert, dass Ihr Code mit vielen struct Definitionen übersät ist. Es ist einfacher für die Person, die das Schreiben von Code, und für andere verwenden es, wenn Sie nur dokumentieren, was jedes Element in dem Tupel ist, anstatt Ihre eigene Struktur zu schreiben / der Menschen die Struktur Definition nachschlagen.

Tupeln wird einfacher zu schreiben - keine Notwendigkeit, eine neue Struktur für jede Funktion zu erstellen, die etwas zurückgibt. Dokumentation über das, was wo geht in die Funktion Dokumentation gehen, die ohnehin benötigt werden. Um die Funktion eines verwenden müssen die Funktion Dokumentation in jedem Fall lesen und das Tupel wird dort erläutert.

Ich stimme Ihnen zu 100% Roddy.

Um mehrere Werte von einer Methode zurückgeben, haben Sie mehrere Möglichkeiten andere als Tupel, die man am besten auf Ihrem Fall ab:

  1. eine neue Struktur anlegen. Das ist gut, wenn die mehrere Werte Sie Rückkehr sind im Zusammenhang , und es ist angebracht, eine neue Abstraktion zu schaffen. Zum Beispiel denke ich „divide_result“ eine gute allgemeine Abstraktion ist, und Leiten dieser Entität um macht den Code viel klarer als nur um ein namenlos Tupel übergeben. Sie könnten dann Methoden erstellen, die auf die neue Art arbeiten, wandeln sie in anderen numerischen Typen, etc.

  2. Mit "Out" Parameter. Übergeben mehrerer Parameter durch Bezugnahme eingeschlossen, und mehrere Werte zurückkehren, indem auf die jeweils out-Parameter zuzuweisen. Dies ist sinnvoll, wenn Ihre Methode gibt mehr nicht verwandt Stücke von Informationen. eine neue Struktur in diesem Fall Erstellen von zu viel des Guten wäre, und mit Out-Parameter Sie diesen Punkt betonen, und jedes Element bekommt den Namen es verdient.

Tupeln sind schlecht.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top