2010-07-04 3 views
7

Beim Betrachten der Elementfunktionen der STL-Container ist mir ein seltsamer Gedanke aufgefallen. Warum haben Funktionen wie std::vector<T>::push_back(T) keinen (optionalen) Rückgabewert (Iterator oder gar einen Verweis auf das angehängte Objekt)? Ich weiß, std::string Funktionen wie insert und erase Iteratoren zurückgeben, aber das ist aus offensichtlichen Gründen. Ich denke, es würde oft eine zweite Codezeile speichern, die häufig auf diese Funktionsaufrufe folgt.Rückgabewerte der STL-Containerfunktion

Ich bin sicher, dass die Designer von C++ einen sehr guten Grund haben, bitte erleuchte mich :)

UPDATE: Ich bin auch eine reale Codebeispiel hier, wo es Codelänge reduzieren könnte:

if(m_token != "{") 
{ 
    m_targets.push_back(unique_ptr<Target>(new Dough(m_token))); 
    return new InnerState(*(m_targets.back()), this); 
} 

könnte

if(m_token != "{") 
    return new InnerState(*(m_targets.push_back(unique_ptr<Target>(new Dough(m_token)))), this); 

reduziert werden, wenn ich std::list::push_back gibt einen Verweis auf das hinzugefügte Element übernehmen. Der Code ist ein bisschen schwer, aber das ist meistens (zwei Sätze von Klammern) aufgrund unique_ptr 's Konstruktor und Dereferenzierung es. Vielleicht aus Gründen der Übersichtlichkeit eine Version ohne Zeiger:

if(m_token != "{") 
{ 
    m_targets.push_back(Dough(m_token)); 
    return new InnerState(m_targets.back(), this); 
} 

gegen

if(m_token != "{") 
    return new InnerState(m_targets.push_back(Dough(m_token)), this); 

Antwort

5

Rückgabe des hinzugefügten Elements oder des Containers in Container-Member-Funktionen ist nicht möglich ein sicherer Weg. STL-Container bieten meist die "strong guarantee". Die Rückgabe des manipulierten Elements oder des Containers würde es unmöglich machen, die starke Garantie zu geben (es würde nur die "Grundgarantie" geben). Der Grund dafür ist, dass das Zurückgeben von etwas möglicherweise einen Kopierkonstruktor aufrufen könnte, der eine Ausnahme auslösen könnte. Aber die Funktion ist bereits beendet, also hat sie ihre Hauptaufgabe erfolgreich erfüllt, aber dennoch eine Ausnahme geworfen, die eine Verletzung der starken Garantie darstellt. Du denkst vielleicht: "Na dann lass uns per Referenz zurück!", Das hört sich nach einer guten Lösung an, ist aber auch nicht absolut sicher. Betrachten Sie folgendes Beispiel:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass& 

Dennoch, wenn der Kopierzuweisungsoperator führt, wissen wir nicht, ob push_back oder nicht succeded und damit indirekt die starke-Garantie zu verletzen. Auch wenn dies keine direkte Verletzung ist. Natürlich würde die Verwendung von MyClass& bar = //... stattdessen dieses Problem beheben, aber es wäre ziemlich unpraktisch, dass ein Container in einen unbestimmten Zustand geraten könnte, nur weil jemand eine & vergessen hat.

Eine ziemlich ähnliche reasoning ist hinter der Tatsache, dass std::stack::pop() nicht den Poped-Wert zurückgibt. Stattdessen gibt top() den obersten Wert auf sichere Weise zurück. Nach dem Aufrufen von top wissen Sie immer noch, dass der Stapel unverändert ist, selbst wenn ein Kopierkonstruktor oder ein Kopierzuweisungskonstruktor ausgibt.

EDIT: Ich glaube, einen Iterator für das neu hinzugefügte Element zurückkehren sollte absolut sicher sein, wenn die Kopie-Konstruktor des Iterator-Typs die Garantie nicht Wurf bietet (und jeder ich kenne tut).

+0

Wow. Tolle Infos, danke! Ich frage mich immer noch, warum sie natürlich keinen Iterator zurückgegeben haben :). Vielleicht weil du dann 'MyClass bar = * (myvector.push_back (functionReturningMyClass()));' haben könntest und wahrscheinlich das gleiche Problem hast wie beim Zurückgeben einer Referenz (oder nicht?). – rubenvb

+0

Ich vermute, der Grund waren Leistungsprobleme, da diese Sache die Konstruktion eines Iterators erfordern würde, und das Kopieren dieses Iterators, und das ist ziemlich viel Aufwand, wenn der Rückgabewert nicht verwendet wird. – smerlin

+0

würde kein vernünftiger Compiler so etwas optimieren? Wahrscheinlich nicht, was das Standardkomitee anschaute :) – rubenvb

-2

Nicht sicher hatten sie einen sehr guten Grund, aber diese Funktion langsam genug ohnehin schon ist.

+4

Wie so, "langsam"? und welchen Unterschied würde ein Rückgabewert machen? –

+0

Da der Rückgabewert meistens in einigen Registern zurückgegeben wird, besteht die Möglichkeit, dass er stillschweigend bereits einen Zeiger auf das eingefügte Element zurückgibt. Und es würde keinen großen Unterschied machen, da der Vektor :: back() konstant ist, so dass ein Iterator (oder Zeiger) trotzdem durch den Vektor gespeichert werden muss. – flownt

+3

Langsam im Vergleich zu was? – GManNickG

5

Interessante Frage. Der offensichtliche Rückgabewert wäre es, den Vektor (oder was auch immer), dass die Operation stattfindet, auf, so dass Sie könnte dann Code schreiben wie:

if (v.push_back(42).size() > n) { 
    // do something 
} 

ich persönlich nicht diesen Stil mögen, aber ich kann nicht glauben, ein guter Grund, es nicht zu unterstützen.

+0

Ich dachte mehr in den Zeilen 'element = v.push_back (i); element.somefunction (someVariable); ', aber deins würde natürlich auch funktionieren. – rubenvb

0

Ich denke, es hat mit dem Konzept eines Rückgabewerts zu tun: der Rückgabewert gibt es nicht für Ihre Bequemlichkeit, sondern für ein konzeptionelles Ergebnis der "Berechnung" sie scheinbar dachte push_back konzeptionell nicht zu nichts.

3

Weil es .back() gibt, die es sofort für Sie zurückgibt?

Konzeptionell werden die C++ - Designer in einer Elementfunktion nichts implementieren, was für Sie an einer öffentlichen Schnittstelle schwierig oder unmöglich wäre. Der Aufruf von .back() ist einfach und einfach genug. Für einen Iterator könnten Sie (Ende - 1) oder nur auto it = end; it--;

Das Standards Committee macht neuen Code möglich und massiv vereinfacht Code, der sehr häufig verwendet wird. So etwas steht nicht auf der Liste der Dinge, die man machen kann.

+0

Genauigkeit: Stellen Sie sicher, dass sich ein Element im Container befindet, bevor Sie '--' auf' end' aufrufen. Ansonsten ist es undefiniertes Verhalten. –

+0

Das ist die gleiche Einschränkung wie '.back()' hat, und eine Nachbedingung von push_back() ist erfolgreich (d. H. Nicht werfen) – MSalters

0

Ich bin nicht sicher, aber ich glaube, dass einer der Gründe, warum die mutierenden std::string Mitglieder eine iterator Rückkehr ist so, dass der Programmierer, ohne dass ein zweiten "einen nicht-const-Iterator auf die std::string nach einer mutierenden Operation erhalten könnte Leck".

Die Schnittstelle std::basic_string wurde entwickelt, um ein Muster namens copy-on-write zu unterstützen, was im Grunde bedeutet, dass jede Mutationsoperation die Originaldaten nicht beeinträchtigt, sondern eine Kopie.Wenn Sie beispielsweise die Zeichenfolge "abcde" hatten und 'a' durch 'z' ersetzt wurden, um "zbcde" zu erhalten, könnten die Daten für die resultierende Zeichenfolge eine andere Position im Heap belegen als die Daten für die ursprüngliche Zeichenfolge.

Wenn Sie eine nicht-const-Iterator eines std::string, dann eine Kuh String-Implementierung erhalten muss eine Kopie (auch als „Leck das Original“ genannt). Andernfalls kann das Programm die zugrunde liegenden Daten ändern (und verletzt die Nur-Lese-invariant) with:

char& c0 = *str.begin(); 
c0 = 'z'; 

Aber nach einer Reihe Mutationsoperation, das resultierende String-Objekt hat bereits alleinigen Besitz der Daten, so dass die Zeichenfolge Die Implementierung muss ihre Daten nicht ein zweites Mal verlieren, um einen Nicht-Konst-Iterator zu erstellen.

Ein std::vector ist anders, weil es keine Copy-on-Write-Semantik unterstützt.

Hinweis: Ich habe den Begriff Leck aus der libstdC++ - Implementierung von std::basic_string. Das "Lecken der Daten" bedeutet auch nicht, dass die Implementierung leaks memory.

EDIT: Hier ist die libstdC++ Definition std::basic_string<CharT, Traits, Alloc>::begin() Referenz:

iterator 
begin() 
{ 
    _M_leak(); 
    return iterator(_M_data()); 
} 
2
v.insert(v.end(),x); 

Würde gleichwertig sein mit wiederkehr einen Iterator auf push_back. Warum push_back selbst keinen Iterator zurückgibt, ist mir ein Rätsel.

0

Vielleicht, weil es nicht "benötigt" wurde?

erase() und insert() haben keine andere Möglichkeit, als einen Iterator zurückgeben eine Schleife zu ermöglichen, weiterhin in genannt wurde.

Ich habe keinen guten Grund sieht die gleiche Logik mit push_back() zu unterstützen.

Aber sicher wäre es wunderbar, mehr kryptische Ausdrücke zu machen. (Ich sehe keine Verbesserung in Ihrem Beispiel, es sieht aus wie eine gute Möglichkeit, Ihre Mitarbeiter zu verlangsamen, wenn Sie Ihren Code lesen ...)