Wie lese ich den gesamten Stream in einen std :: string?

76

Ich versuche, einen ganzen Stream (mehrere Zeilen) in eine Zeichenfolge einzulesen.

Ich benutze diesen Code und er funktioniert, aber er verletzt meinen Sinn für Stil ... Sicher gibt es einen einfacheren Weg? Vielleicht mit Stringstreams?

void Obj::loadFromStream(std::istream & stream)
{ 
  std::string s;

  std::streampos p = stream.tellg();  // remember where we are

  stream.seekg(0, std::ios_base::end); // go to the end
  std::streamoff sz = stream.tellg() - p;  // work out the size
  stream.seekg(p);        // restore the position

  s.resize(sz);          // resize the string
  stream.read(&s[0], sz);  // and finally, read in the data.


Eigentlich würde ein constVerweis auf eine Zeichenfolge auch genügen, und das könnte die Sache einfacher machen ...

const std::string &s(... a miracle occurs here...)
Roddy
quelle

Antworten:

118

Wie wäre es mit

std::istreambuf_iterator<char> eos;
std::string s(std::istreambuf_iterator<char>(stream), eos);

(könnte ein Einzeiler sein, wenn nicht für MVP)

Nach der Bearbeitung nach 2011 wird dieser Ansatz jetzt geschrieben

std::string s(std::istreambuf_iterator<char>(stream), {});
Cubbi
quelle
2
Es könnte immer noch ein Einzeiler sein, wenn Sie wollen : string s = string(...).
Mike Seymour
1
Vielen Dank. Können Sie näher erläutern, was das tut? Muss eos nicht irgendwie initialisiert werden?
Roddy
13
@Roddy: Die Zeichenfolge besteht aus dem Bereich istreambuf_iterator, der über unformatierte Zeichen iteriert, bis er einem standardmäßig erstellten Eingabe-Iterator entspricht, der auch als "Ende des Streams" bezeichnet wird. Siehe Scott Meyers, Effektive STL Punkt 29: Betrachten Sie istreambuf_iterators für die zeichenweise Eingabe
Cubbi
1
Entnommen aus der Dokumentation zu std :: istream_iterator "Hinweise: Beim Lesen von Zeichen überspringt std :: istream_iterator standardmäßig Leerzeichen (sofern dies nicht mit std :: noskipws oder einem gleichwertigen Element deaktiviert ist), std :: istreambuf_iterator jedoch nicht. Außerdem std :: istreambuf_iterator ist effizienter, da der Aufwand für das einmalige Erstellen und Zerstören des Wachobjekts pro Zeichen vermieden wird. "
Paul Solt
Das Lesen von Byte für Byte ist aus dem Leistungs-POV nicht ganz effizient. Gibt es eine bessere Lösung, die größere Teile einliest? Vielleicht SSE nutzen?
Ajeh
27

Ich bin zu spät zur Party, aber hier ist eine ziemlich effiziente Lösung:

std::string gulp(std::istream &in)
{
    std::string ret;
    char buffer[4096];
    while (in.read(buffer, sizeof(buffer)))
        ret.append(buffer, sizeof(buffer));
    ret.append(buffer, in.gcount());
    return ret;
}

Ich habe ein Benchmarking durchgeführt und es stellt sich heraus, dass die std::istreambuf_iteratorTechnik ( die von der akzeptierten Antwort verwendet wird ) tatsächlich viel langsamer ist. Bei gcc 4.4.5 mit -O3ist es ungefähr ein 4,5-facher Unterschied auf meinem Computer, und die Lücke wird mit niedrigeren Optimierungseinstellungen größer.

Joey Adams
quelle
5
In der Tat effizienter als meine Antwort, wie ein richtiges blockweises Lesen wäre. OP wollte jedoch den "einfachen" Weg, der oft das Gegenteil von "schnell" ist.
Cubbi
8
Die Verwendung von string :: Reserve (size_t) würde es noch effizienter machen.
Tim
Joey, optimiere mit -O2. Option -O3 ist nicht für den schnellsten, sondern für kompakten Code, wie ich mich erinnere.
Barney Szabolcs
2
@BarnabasSzabolcs: -Os steht für kompakten Code, -O3 für aggressive Optimierung und -O2 für weniger aggressive. Siehe gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Joey Adams
In einigen Fällen müssen Sie mit verschiedenen Einstellungen experimentieren. Bestimmte Optimierungen verringern in bestimmten Fällen die Geschwindigkeit. (Mein Kommentar zu O2 / O2 ist aufgrund dieses Arguments ebenfalls falsch.) Siehe z. B. stackoverflow.com/questions/19470873/…
Barney Szabolcs
20

Du könntest es tun

std::string s;
std::ostringstream os;
os<<stream.rdbuf();
s=os.str();

aber ich weiß nicht, ob es effizienter ist.

Alternative Version:

std::string s;
std::ostringstream os;
stream>>os.rdbuf();
s=os.str();
Matteo Italia
quelle
1
Vielen Dank. Als Lösung finde ich das wirklich einfach und lesbar und benutze es. Ich akzeptierte jedoch Cubbis Antwort, da ich viel daraus gelernt hatte!
Roddy
Ja, dies ist die einzige Methode "Bis Eofbit lesen". Die anderen Methoden ( istream::get(streambuf*)und std::getline(istream, string)) lesen nur bis zu einem bestimmten Trennzeichen.
Tanz87
11

Sie können versuchen, etwas von Algorithmen zu verwenden. Ich muss mich auf die Arbeit vorbereiten, aber hier ist ein sehr kurzer Überblick über die Dinge (es muss einen besseren Weg geben):

copy( istreambuf_iterator<char>(stream), istreambuf_iterator<char>(), back_inserter(s) );
Wheaties
quelle
0

Nun, wenn Sie nach einer einfachen und "lesbaren" Möglichkeit suchen, dies zu tun. Ich würde empfehlen, ein hochrangiges Framework für Ihr Projekt hinzuzufügen / zu verwenden. Dafür benutze ich immer Poco und Boost für alle meine Projekte. In diesem Fall mit Poco:

    string text;
    FileStream fstream(TEXT_FILE_PATH);
    StreamCopier::copyToString(fstream, text);
wdavilaneto
quelle
0

Was ist mit getline mit Trennzeichen? Der nächste Code hilft mir, das ganze std :: cin auf Ubuntu mit g ++ - 10 in einen String zu lesen.

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s;

    getline(cin, s, {}); //the whole stream into variable s

    return 0;
}
Vahag Chakhoyan
quelle
-1

Vielleicht diese 1 Zeile C ++ 11 Lösung:

std::vector<char> s{std::istreambuf_iterator<char>{in},{}};
user7223715
quelle
std::vector<char>ist kein String.
Val sagt Reinstate Monica