Warum ist die Verwendung von Tupeln in C ++ nicht häufiger?

124

Warum scheint niemand in C ++ Tupel zu verwenden, auch nicht die Boost Tuple Library noch die Standardbibliothek für TR1? Ich habe viel C ++ - Code gelesen und sehe sehr selten die Verwendung von Tupeln, aber ich sehe oft viele Stellen, an denen Tupel viele Probleme lösen würden (normalerweise werden mehrere Werte von Funktionen zurückgegeben).

Mit Tupeln kannst du alle möglichen coolen Dinge wie diese machen:

tie(a,b) = make_tuple(b,a); //swap a and b

Das ist sicherlich besser als das:

temp=a;
a=b;
b=temp;

Natürlich können Sie dies immer tun:

swap(a,b);

Aber was ist, wenn Sie drei Werte drehen möchten? Sie können dies mit Tupeln tun:

tie(a,b,c) = make_tuple(b,c,a);

Tupel machen es auch viel einfacher, mehrere Variablen von einer Funktion zurückzugeben, was wahrscheinlich ein viel häufigerer Fall ist als das Austauschen von Werten. Die Verwendung von Verweisen auf Rückgabewerte ist sicherlich nicht sehr elegant.

Gibt es große Nachteile von Tupeln, an die ich nicht denke? Wenn nicht, warum werden sie selten verwendet? Sind sie langsamer? Oder sind die Leute einfach nicht an sie gewöhnt? Ist es eine gute Idee, Tupel zu verwenden?

Zifre
quelle
17
+1 für cleveren Tupel-Tausch-Trick :)
kizzx2
10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset
3
IMO-Tupel eignen sich für schwache Schreibsprachen oder Sprachen, in denen es sich um native Strukturen handelt. Zum Beispiel in Python oder PHP erleichtern sie nur das Leben, während in C ++ zu viel eingegeben wird (um es aus einer Vorlage zu erstellen) und zu wenig Vorteile.
Doc
4
Ein Kommentar zum OP: Ich denke, dass die derzeit akzeptierte Antwort bereits so weit veraltet ist, dass sie sachlich falsch ist. Möglicherweise möchten Sie die Auswahl der akzeptierten Antwort überdenken.
Ulidtko
8
@ GerardoMarset Ist das dein Ernst?
Heilige

Antworten:

43

Weil es noch nicht Standard ist. Alles, was nicht dem Standard entspricht, hat eine viel höhere Hürde. Pieces of Boost sind populär geworden, weil Programmierer nach ihnen verlangten. (hash_map fällt mir ein). Obwohl Tupel praktisch ist, ist es kein so überwältigender und klarer Gewinn, dass sich die Leute damit beschäftigen.

Alan De Smet
quelle
1
Die Leute scheinen andere Teile von Boost wie verrückt zu benutzen. Obwohl Hash-Maps im Allgemeinen sicherlich viel nützlicher sind als Tupel.
Zifre
Ich weiß nicht genau, was Sie sehen, aber ich vermute, dass die Teile, die die Leute wie verrückt verwenden, Funktionen sind, die sie wirklich, wirklich wollten. Somit (wieder erraten) die Popularität der Hash-Karte, des gezählten Zeigers und dergleichen. Das Tupel ist praktisch, aber es springt nicht heraus, um ein Loch zu füllen. Die Auszahlung ist nicht offensichtlich. Wie oft müssen Sie genau N Objekte drehen? (Im Gegensatz zu der Notwendigkeit, beliebig lange Vektoren drehen zu müssen). Und Menschen sind es gewohnt, entweder Rückgabewerte als Referenz zu übergeben oder kleine Klassen oder Strukturen zurückzugeben.
Alan De Smet
20
Es ist jetzt tatsächlich Teil des C ++ 11-Standards: en.cppreference.com/w/cpp/utility/tuple
Roman Susi
124

Eine zynische Antwort ist, dass viele Leute in C ++ programmieren, aber die übergeordneten Funktionen nicht verstehen und / oder verwenden. Manchmal liegt es daran, dass sie nicht erlaubt sind, aber viele versuchen es einfach nicht (oder verstehen es sogar nicht).

Als Beispiel ohne Boost: Wie viele Leute verwenden die Funktionen von <algorithm> ?

Mit anderen Worten, viele C ++ - Programmierer sind einfach C-Programmierer, die C ++ - Compiler verwenden, und vielleicht std::vectorund std::list. Dies ist ein Grund, warum die Verwendung von boost::tuplenicht häufiger ist.

Trey Jackson
quelle
18
-1 von mir, weil C ++ - Programmierer nicht so dumm sind, wie diese Antwort sie klingen lässt.
user541686
5
@Mehrdad Nachdem ich viel kommerziellen und nicht kommerziellen C ++ - Code durchgesehen und Tonnen von C ++ - Material gelesen habe, kann ich mit ziemlicher Sicherheit sagen, dass ein sehr großer Teil der "C ++" - Entwickler nur C-Entwickler sind, die keinen reinen bekommen konnten C-Compiler. Vorlagen fehlen beispielsweise in den meisten Materialien fast vollständig (etwas, das ich sehr zu lieben gelernt habe). Seltsame Makro-Hacks sind häufig und der Namespace wird stark unterbeansprucht.
Klarer
5
Unsinn Antwort. Wenn man sie braucht, werden sie sie einholen. Sie werden nicht benötigt; Sie werden also nicht verwendet. Zu sagen, dass sie nicht verwendet werden, weil sie nicht leicht zu verstehen sind, ist schlecht.
Michael Chourdakis
9
@ Michael Nonsense Kommentar. Wenn Sie eine vollständige Turing-Teilmenge einer Sprache haben, wird beim Programmieren nichts mehr benötigt . Mangelnde Nutzung bedeutet sicherlich nicht, dass jeder die übergeordneten C ++ - Konstrukte versteht und sich dafür entscheidet, sie nicht zu verwenden.
Trey Jackson
4
Tbh Ich hatte NIE das Bedürfnis nach std :: tuple außerhalb der Metaprogrammierung variabler Vorlagen. Es gab keinen Punkt im Leben, an dem ich mich mit einem traurigen Gesicht hinsetzte und dachte "Wenn ich nur diese Tupel hätte". Wenn ich mir Tupel anschaue, denke ich: "Wofür zum Teufel würde jemand, der gesund ist, sie brauchen (ich würde mich nicht für gesund halten)". Leute außerhalb der Metaprogrammierung scheinen sie als "anonyme Struktur" zu verwenden, was so hässlich ist und auf ein starkes Fehlen von Codequalität und Wartbarkeit in ihren Köpfen hinweist.
Heilige
23

Die C ++ - Tupelsyntax kann etwas ausführlicher sein, als die meisten Leute möchten.

Erwägen:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

Wenn Sie also Tupel ausgiebig nutzen möchten, erhalten Sie entweder überall Tupel-Typedefs oder überall ärgerlich lange Typnamen. Ich mag Tupel. Ich benutze sie bei Bedarf. Normalerweise ist es jedoch auf einige Situationen beschränkt, z. B. einen N-Element-Index oder die Verwendung von Multimaps zum Binden der Bereichsiteratorpaare. Und es ist normalerweise in einem sehr begrenzten Umfang.

Im Vergleich zu etwas wie Haskell oder Python sieht alles sehr hässlich und hackig aus. Wenn C ++ 0x hier ankommt und wir die Schlüsselwort-Tupel "Auto" erhalten, werden sie viel attraktiver.

Die Nützlichkeit von Tupeln ist umgekehrt proportional zur Anzahl der Tastenanschläge, die zum Deklarieren, Packen und Entpacken erforderlich sind.

user21714
quelle
Die meisten Leute werden "Namespace-Boost verwenden"; und müssen nicht boost :: eingeben. Ich denke nicht, dass das Tippen von Tupeln so ein Problem ist. Das heißt, ich denke, Sie haben einen Punkt. Auto könnte viel mehr Leute dazu bringen, Tupel zu benutzen.
Zifre
2
@Zifre: Das Problem ist, dass Sie in einer Header-Datei nicht "Namespace X verwenden" sollten, da dies die Verschmutzung des Namespaces erzwingt und die Namespaces unterwandert.
Herr Fooz
1
Ah ja, ich vergesse die Überschriften. Aber innerhalb des Programmcodes müssen Sie sich keine Sorgen machen. Und sobald wir C ++ 0x haben, können wir auto verwenden, wodurch ein Großteil der Eingabe entfällt.
Zifre
19
Ist es nur ich Ich denke nicht, dass das Speichern der 7 Zeichen von "boost ::" das ist, worauf er sich bezog, sondern die anderen 33 Zeichen . Das ist eine Menge Typisierung von Klassennamen, besonders wenn auch diese einen Namespace-Bereich haben. Nehmen Sie als lächerliches Beispiel boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName >>.
Oger Psalm33
10

Für mich ist es Gewohnheit, zweifellos: Tupel lösen keine neuen Probleme für mich, nur ein paar, mit denen ich schon gut umgehen kann. Das Tauschen von Werten fühlt sich auf altmodische Weise immer noch einfacher an - und was noch wichtiger ist, ich denke nicht wirklich darüber nach, wie man "besser" tauscht. Es ist gut genug wie es ist.

Persönlich denke ich nicht, dass Tupel eine großartige Lösung für die Rückgabe mehrerer Werte sind - das klingt nach einem Job für structs.

ojrac
quelle
4
'Ich denke nicht wirklich darüber nach, wie ich "besser" tauschen kann.' - Wenn ich Code schreibe, schreibe ich Fehler. Durch die Reduzierung der Codekomplexität wird die Anzahl der von mir geschriebenen Fehler verringert. Ich hasse es, immer wieder dieselben Fehler zu erstellen. Ja, ich denke darüber nach, wie man <strike> Code </> besser austauscht . Weniger bewegliche Teile (LOC, temporäre Variablen, zu tippende Bezeichner), besser lesbarer Code; Guter Code.
sehe
Zustimmen. Eine in Auto-Pointer oder Smart Pointer eingeschlossene Klasse ist typspeichernd. Ich habe einmal Tupel verwendet, dann aber den Code mithilfe von Klassen neu geschrieben. retValue.state ist klarer als retValue.get <0> ().
Valentin Heinitz
1
@sehe: Besseren, besser lesbaren Code zu schreiben ist auch mein Ziel. Das Hinzufügen weiterer Syntaxtypen ist mit Kosten verbunden, und ich glaube nicht, dass "besseres Austauschen" den mentalen Aufwand rechtfertigt, über noch mehr Syntaxtypen für jede von Ihnen gelesene Codezeile nachzudenken.
Ojrac
8

Aber was ist, wenn Sie drei Werte drehen möchten?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

OK, also mit 4 usw. Werten wird das n-Tupel schließlich weniger Code als n-1 Swaps. Und mit dem Standard-Swap werden 6 Zuweisungen anstelle der 4 ausgeführt, die Sie hätten, wenn Sie selbst eine Vorlage mit drei Zyklen implementiert hätten, obwohl ich hoffen würde, dass der Compiler dies für einfache Typen lösen würde.

Sie können Szenarien entwickeln, in denen Swaps unhandlich oder unangemessen sind, zum Beispiel:

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

ist etwas umständlich auszupacken.

Punkt ist jedoch, dass es bekannte Wege gibt, mit den häufigsten Situationen umzugehen, für die Tupel gut sind, und daher keine große Dringlichkeit, Tupel aufzunehmen. Wenn nichts anderes, bin ich nicht sicher, dass:

tie(a,b,c) = make_tuple(b,c,a);

macht keine 6 Kopien, was es für einige Typen völlig ungeeignet macht (Sammlungen sind die offensichtlichsten). Fühlen Sie sich frei, mich davon zu überzeugen, dass Tupel eine gute Idee für "große" Typen sind, indem Sie sagen, dass dies nicht so ist :-)

Für die Rückgabe mehrerer Werte sind Tupel perfekt, wenn die Werte nicht kompatibel sind. Einige Leute mögen sie jedoch nicht, wenn der Aufrufer sie in die falsche Reihenfolge bringen kann. Einige Leute mögen mehrere Rückgabewerte überhaupt nicht und möchten ihre Verwendung nicht fördern, indem sie sie einfacher machen. Einige Leute bevorzugen nur benannte Strukturen für In- und Out-Parameter und konnten wahrscheinlich nicht mit einem Baseballschläger überredet werden, Tupel zu verwenden. Keine Berücksichtigung des Geschmacks.

Steve Jessop
quelle
1
Sie würden definitiv nicht Vektoren mit Tupeln tauschen wollen. Ich denke, drei Elemente auszutauschen ist bei Tupeln sicherlich klarer als bei zwei. Bei mehreren Rückgabewerten sind out-Parameter schlecht, Strukturen werden extra typisiert, und es gibt definitiv Fälle, in denen mehrere Rückgabewerte erforderlich sind.
Zifre
Ich weiß , warum Sie Tupel verwenden (und ich weiß, warum ich sie verwenden würde, wenn sich die Gelegenheit ergab, obwohl ich nicht glaube, dass dies jemals der Fall war). Ich vermute, warum andere Leute sie nicht benutzen, auch wenn sie sich ihrer bewusst sind. zB weil sie nicht einverstanden sind mit "out params are evil" ...
Steve Jessop
Wissen Sie, ob wir "tie (a, b, c) = make_tuple (b, c, a)" ersetzen können? durch "binden (a, b, c) = binden (b, c, a);" ?
Rexxar
2
Ein Unentschieden (technisch gesehen eine Stufe) ist ein Tupel, das mit nicht konstanten Referenzen erstellt wurde. Ich kann keine Boost-Dokumentation finden, die besagt, was operator = und den Kopierkonstruktor für tie / tuple garantiert, wenn einige der beteiligten Referenzen denselben Verweis haben. Aber das müssen Sie wissen. Eine naive Implementierung von operator = könnte eindeutig sehr schief gehen ...
Steve Jessop
1
@Steve: Und da Bindungen sind alle über Kopien zu verhindern (sie für nicht-kopierbaren Typen funktionieren sollte, beachten Sie, dass die LHS etwas sein könnte ganz anderes) in der Tat alles sollte sehr schief gehen (man denke an nicht-POD - Klasse Objekte). Stellen Sie sich vor, Sie würden dieselbe Logik schreiben, ohne Temps zu verwenden.
sehe
7

Wie viele Leute betonten, sind Tupel einfach nicht so nützlich wie andere Funktionen.

  1. Die tauschenden und rotierenden Gimmicks sind nur Gimmicks. Sie sind äußerst verwirrend für diejenigen, die sie noch nie gesehen haben, und da es so ziemlich jeder ist, sind diese Gimmicks nur eine schlechte Softwareentwicklungspraxis.

  2. Das Zurückgeben mehrerer Werte mithilfe von Tupeln ist viel weniger selbstdokumentierend als die Alternativen - das Zurückgeben benannter Typen oder die Verwendung benannter Referenzen. Ohne diese Selbstdokumentation ist es leicht, die Reihenfolge der zurückgegebenen Werte zu verwechseln, wenn sie sich gegenseitig konvertieren lassen und nicht klüger sind.

igorlord
quelle
6

Nicht jeder kann Boost verwenden, und TR1 ist noch nicht weit verbreitet.

Brian Neal
quelle
3
Viele Leute benutzen Boost. Diese Leute könnten auch Tupel benutzen.
Zifre
3
Sie haben gefragt, warum die Leute sie nicht benutzen, und ich habe eine Antwort gegeben.
Brian Neal
2
An den Down-Wähler: Ich arbeite an einem Ort, an dem es politisch unmöglich ist, Boost zu verwenden, und selbst zu diesem Zeitpunkt unterstützt die von uns verwendete Compiler-Toolkette (für ein eingebettetes System) TR1 / C ++ 11 nicht.
Brian Neal
5

Bei Verwendung von C ++ auf eingebetteten Systemen wird das Abrufen von Boost-Bibliotheken komplex. Sie koppeln miteinander, sodass die Bibliotheksgröße zunimmt. Sie geben Datenstrukturen zurück oder verwenden die Parameterübergabe anstelle von Tupeln. Bei der Rückgabe von Tupeln in Python ist die Datenstruktur in der Reihenfolge und Art der zurückgegebenen Werte nicht explizit.

DanM
quelle
5

Sie werden selten gesehen, weil gut gestalteter Code sie normalerweise nicht benötigt. In der Natur gibt es nicht viele Fälle, in denen die Verwendung einer anonymen Struktur der Verwendung einer benannten Struktur überlegen ist. Da alles, was ein Tupel wirklich darstellt, eine anonyme Struktur ist, passen die meisten Codierer in den meisten Situationen einfach zur Realität.

Angenommen, wir haben eine Funktion "f", bei der eine Tupelrückgabe sinnvoll sein könnte. In der Regel sind solche Funktionen so kompliziert, dass sie fehlschlagen können.

Wenn "f" fehlschlagen kann, benötigen Sie eine Statusrückgabe. Schließlich möchten Sie nicht, dass Anrufer jeden Parameter überprüfen müssen, um einen Fehler zu erkennen. "f" passt wahrscheinlich in das Muster:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

Das ist nicht schön, aber schauen Sie, wie hässlich die Alternative ist. Beachten Sie, dass ich noch einen Statuswert benötige, der Code jedoch nicht mehr lesbar und nicht kürzer ist. Es ist wahrscheinlich auch langsamer, da mir die Kosten für 1 Kopie mit dem Tupel entstehen.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

Ein weiterer bedeutender Nachteil ist hier verborgen: Mit "ReturnInts" kann ich die Rückgabe von "f" ändern, indem ich "ReturnInts" ändere, ohne die Schnittstelle von "f" zu ändern. Die Tupellösung bietet diese wichtige Funktion nicht, was sie zur schlechteren Antwort für jeden Bibliothekscode macht.

Zack Yezek
quelle
1
Ausnahmen machen diese Schnittstelle viel sauberer.
David Stone
Um fair zu sein (und extrem spät zur Party), können Sie die Lesbarkeit verbessern, indem Sie den Code festlegen using std::tuple;und einfach verwenden tuple.
Alrekr
2
Durch tupledie Verwendung wird der Code weniger lesbar, nicht mehr. Der meiste Code enthält heutzutage eine sehr große Anzahl von Symbolen - das Sehen std::tuplemacht dem Auge genau klar, was es ist.
Tom Swirly
3

Natürlich können Tupel nützlich sein, aber wie bereits erwähnt, gibt es ein bisschen Overhead und ein oder zwei Hürden, durch die man springen muss, bevor man sie überhaupt wirklich benutzen kann.

Wenn Ihr Programm ständig Orte findet, an denen Sie mehrere Werte zurückgeben oder mehrere Werte austauschen müssen, lohnt es sich möglicherweise, die Tupelroute zu wählen. Andernfalls ist es manchmal einfacher, die Dinge auf klassische Weise zu erledigen.

Im Allgemeinen ist Boost noch nicht bei allen installiert, und ich würde sicherlich nicht die Mühe machen, es herunterzuladen und meine Include-Verzeichnisse so zu konfigurieren, dass es nur für seine Tupel-Funktionen damit funktioniert. Ich denke, Sie werden feststellen, dass Leute, die Boost bereits verwenden, eher Tupelverwendungen in ihren Programmen finden als Nicht-Boost-Benutzer, und Migranten aus anderen Sprachen (Python kommt in den Sinn) sind eher verärgert über das Fehlen von Tupeln in C ++ als Methoden zum Hinzufügen von Tupelunterstützung zu untersuchen.

Zac
quelle
1

Da ein Datenspeicher std::tupledie schlechtesten Eigenschaften sowohl eines structals auch eines Arrays aufweist; Jeder Zugriff basiert auf der n-ten Position, aber man kann tupleeine forSchleife nicht durchlaufen .

Wenn die Elemente in tuplekonzeptionell ein Array sind, verwende ich ein Array. Wenn die Elemente konzeptionell kein Array sind, ist eine Struktur (die Elemente benannt hat) besser zu warten. ( a.lastnameist erklärender als std::get<1>(a)).

Damit bleibt die vom OP erwähnte Transformation der einzige praktikable Anwendungsfall für Tupel.

Doron
quelle
0

Ich habe das Gefühl, dass viele Boost.Any und Boost.Variant (mit etwas Technik) anstelle von Boost.Tuple verwenden.

dirkgently
quelle
Warum sollten Sie effizientes statisches Tippen gegen so etwas eintauschen?
Zifre
Boost.Variant ist absolut typsicher.
user21714
1
Hoppla, ja, es ist typsicher, aber es wird zur Laufzeit eingegeben.
Zifre
5
Ich sehe nicht, wie Tuple Any / Variant ersetzt werden könnte. Sie machen nicht das Gleiche.
Mankarse
1
@Zifre Ich kann nicht für den Autor sprechen, aber ich denke, die Implikation hier ist, sie zusammen mit anderen Containertypen zu verwenden.
Tim Seguine