web-dev-qa-db-de.com

C++ std :: string append vs Push_back ()

Dies ist wirklich eine Frage nur für mein eigenes Interesse, das ich durch die Dokumentation nicht feststellen konnte. 

Ich sehe auf http://www.cplusplus.com/reference/string/string/ dass das Anfügen komplex ist:

Msgstr "Nicht angegeben, aber im Allgemeinen bis zu linear in der neuen Zeichenfolge."

während Push_back () Komplexität aufweist:

Msgstr "Nicht angegeben; In der Regel konstant amortisiert, jedoch linear bis zur neuen Stringlänge."

Angenommen, ich möchte die Zeichen "foo" an eine Zeichenfolge anhängen. Würde

myString.Push_back('f');
myString.Push_back('o');
myString.Push_back('o');

und 

myString.append("foo");

genau das Gleiche? Oder gibt es da einen Unterschied? Sie könnten feststellen, dass das Anhängen effizienter ist, da der Compiler wissen würde, wie viel Speicher erforderlich ist, um die Zeichenfolge um die angegebene Anzahl von Zeichen zu erweitern, während Push_back möglicherweise den Speicher für jeden Aufruf sichern muss.

19
Memento Mori

In C++ 03 (für das die meisten "cplusplus.com" -Dokumentationen verfasst sind) wurden die Komplexitäten nicht festgelegt, da Bibliotheksimplementierer die Möglichkeit hatten, interne Copy-On-Write- oder "seilartige" Repräsentationen für Strings auszuführen. Beispielsweise kann es bei einer COW-Implementierung erforderlich sein, die gesamte Zeichenfolge zu kopieren, wenn ein Zeichen geändert wird und eine gemeinsame Nutzung stattfindet.

In C++ 11 sind COW- und Seilimplementierungen verboten. Sie sollten eine konstante amortisierte Zeit pro hinzugefügten Zeichen oder eine lineare amortisierte Zeit in der Anzahl der Zeichen erwarten, die am Ende zum Anhängen an eine Zeichenfolge hinzugefügt werden. Implementierer können relativ verrückte Dinge mit Strings tun (im Vergleich zu std::vector), aber die meisten Implementierungen beschränken sich auf Dinge wie die "Optimierung kleiner Strings".

Beim Vergleich von Push_back und append beraubt Push_back die zugrunde liegende Implementierung potenziell nützlicher Längeninformationen, die möglicherweise zur Vorbelegung von Speicherplatz verwendet werden. Andererseits erfordert append, dass eine Implementierung die Eingabe zweimal durchläuft, um diese Länge zu ermitteln. Daher hängt der Leistungsgewinn oder -verlust von einer Reihe nicht erkennbarer Faktoren ab, z. B. der Länge der Zeichenfolge, bevor Sie das Anfügen versuchen . Der Unterschied ist wahrscheinlich extrem extrem EXTREM klein. Gehen Sie dazu mit append - es ist weitaus lesbarer.

32
Billy ONeal

Fügen Sie hier eine weitere Meinung hinzu.

Ich persönlich halte es für besser, Push_back() zu verwenden, wenn Sie nacheinander Zeichen aus einem anderen String hinzufügen. Zum Beispiel:

string FilterAlpha(const string& s) {
  string new_s;
  for (auto& it: s) {
    if (isalpha(it)) new_s.Push_back(it);
  }
  return new_s;
}

Wenn Sie append()here verwenden, würde ich Push_back(it) durch append(1,it) ersetzen, was für mich nicht so lesbar ist.

3
Pei

Ich hatte den gleichen Zweifel, also machte ich einen kleinen Test, um dies zu überprüfen (g ++ 4.8.5 mit C++ 11-Profil unter Linux, Intel, 64-Bit unter VmWare Fusion).

Und das Ergebnis ist interessant:

 Push: 19 
 Anhang: 21 
 ++++: 34 

Möglicherweise liegt dies an der Stringlänge (groß), aber der Operator + ist im Vergleich zum Push_back und zum Anhängen sehr teuer.

Es ist auch interessant, dass der Operator, wenn er nur ein Zeichen (keine Zeichenfolge) empfängt, sich sehr ähnlich wie Push_back verhält. 

Um nicht von vorab zugewiesenen Variablen abhängig zu sein, ist jeder Zyklus in einem anderen Bereich definiert.

Hinweis: Der vCounter verwendet einfach gettimeofday, um die Unterschiede zu vergleichen.

TimeCounter vCounter;

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.Push_back('a');
        vTest.Push_back('b');
        vTest.Push_back('c');
    }
    vCounter.stop();
    cout << "Push :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.append("abc");
    }
    vCounter.stop();
    cout << "append :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest += 'a';
        vTest += 'b';
        vTest += 'c';
    }
    vCounter.stop();
    cout << "++++ :" << vCounter.elapsed() << endl;
}
3
user2583872

Ja, ich würde auch erwarten, dass append() aus den von Ihnen angegebenen Gründen bessere Ergebnisse erzielt. In einer Situation, in der Sie einen String anhängen müssen, ist die Verwendung von append() (oder operator+=) sicherlich vorzuziehen (nicht zuletzt auch, weil der Code viel lesbarer ist).

Der Standard legt jedoch die Komplexität der Operation fest. Und das ist im Allgemeinen auch für append() linear, da letztendlich jedes Zeichen der angehängten Zeichenfolge (und möglicherweise alle Zeichen, falls eine Neuzuweisung erfolgt) kopiert werden muss (dies gilt auch, wenn memcpy oder ähnliches verwendet wird).

0
jogojapan