Jak odczytać cały plik do std::string w C++?

Jak wczytać plik do std::string, czyli przeczytać cały plik na raz?

Osoba wywołująca powinna określić tryb tekstowy lub binarny. Rozwiązanie powinno być zgodne ze standardami, przenośne i wydajne. Nie powinien niepotrzebnie kopiować danych ciągu i powinien unikać realokacji pamięci podczas odczytu ciągu.

Jednym ze sposobów osiągnięcia tego byłoby ustalenie rozmiaru pliku, zmiana rozmiaru std::string i fread() na const_cast<char*>() data() std::string. Wymaga to, aby dane std::string były ciągłe, co nie jest wymagane przez standard, ale wydaje się, że tak jest w przypadku wszystkich znanych implementacji. Co gorsza, jeśli plik będzie czytany w trybie tekstowym, rozmiar std::string może nie być równy rozmiarowi pliku.

W pełni poprawne, zgodne ze standardami i przenośne rozwiązania można skonstruować, używając std::ifstream rdbuf() do std::ostringstream, a stamtąd do std::string. Może to jednak spowodować skopiowanie danych ciągu i/lub niepotrzebne ponowne przydzielenie pamięci.

  • Czy wszystkie odpowiednie implementacje bibliotek standardowych są wystarczająco inteligentne, aby uniknąć niepotrzebnych kosztów ogólnych?
  • Czy jest na to inny sposób?
  • Czy przegapiłem jakąś ukrytą funkcję Boost, która już zapewnia pożądaną funkcjonalność?


void slurp(std::string& data, bool is_binary)

person Community    schedule 22.09.2008    source źródło
comment
Pamiętaj, że nadal masz pewne rzeczy niedookreślone. Na przykład, jakie jest kodowanie znaków w pliku? Czy podejmiesz próbę automatycznego wykrycia (co działa tylko w kilku określonych przypadkach)? Czy uhonorujesz m.in. Nagłówki XML informujące o kodowaniu pliku? Nie ma też czegoś takiego jak tryb tekstowy lub tryb binarny - myślisz o FTP?   -  person Jason Cohen    schedule 22.09.2008
comment
Tryb tekstowy i binarny to hacki specyficzne dla MSDOS i Windows, które próbują obejść fakt, że znaki nowej linii są reprezentowane przez dwa znaki w systemie Windows (CR/LF). W trybie tekstowym są one traktowane jako jeden znak („\n”).   -  person Ferruccio    schedule 22.09.2008
comment
Zwykle takie rzeczy są traktowane przez procedury, które dzielą ciągi znaków na linie, a nie procedury, które odczytują dane z plików. Oznacza to, że w każdym środowisku, w którym programowałem, istnieje jakiś rodzaj funkcji readAsLines() lub breakIntoLines(), która jest inteligentna w takich sytuacjach.   -  person Jason Cohen    schedule 22.09.2008
comment
Chociaż nie jest to (całkiem) dokładny duplikat, jest to ściśle powiązane z: jak wstępnie przydzielić pamięć dla obiektu std::string? (który wbrew powyższemu stwierdzeniu Konrada zawierał kod, który to umożliwia, wczytując plik bezpośrednio do miejsca docelowego, bez wykonywania dodatkowej kopii).   -  person Jerry Coffin    schedule 21.09.2012
comment
sąsiedztwo nie jest wymagane przez normę - tak, jest, w sposób okrężny. Gdy tylko użyjesz op[] na łańcuchu, musi on zostać połączony w ciągły zapisywalny bufor, więc gwarantujemy, że zapis do &str[0] będzie bezpieczny, jeśli najpierw .resize() będzie wystarczająco duży. A w C++ 11 ciąg znaków jest po prostu zawsze ciągły.   -  person Tino Didriksen    schedule 19.07.2013
comment
Powiązany link: Jak czytać plik w C++? — porównuje i omawia różne podejścia. I tak, rdbuf (ten w zaakceptowanej odpowiedzi) nie jest najszybszy, read jest.   -  person legends2k    schedule 27.11.2014
comment
Wszystkie te rozwiązania doprowadzą do źle utworzonych ciągów, jeśli kodowanie/interpretacja plików jest niepoprawna. Miałem naprawdę dziwny problem podczas serializacji pliku JSON w ciąg znaków, dopóki ręcznie nie przekonwertowałem go na UTF-8; Zawsze otrzymywałem tylko pierwszą postać, niezależnie od tego, jakiego rozwiązania próbowałem! Tylko trzeba na to uważać! :)   -  person kayleeFrye_onDeck    schedule 01.11.2018


Odpowiedzi (15)


Jednym ze sposobów jest opróżnienie bufora strumienia do osobnego strumienia pamięci, a następnie przekonwertowanie go na std::string:

std::string slurp(std::ifstream& in) {
    std::ostringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

To jest ładnie zwięzłe. Jednakże, jak zauważono w pytaniu, wykonuje to nadmiarową kopię i niestety zasadniczo nie ma możliwości uniknięcia tej kopii.

Jedynym prawdziwym rozwiązaniem, które pozwala uniknąć zbędnych kopii, jest niestety ręczne odczytywanie w pętli. Ponieważ C++ gwarantuje teraz ciągłe ciągi znaków, można napisać co następuje (≥C++14):

auto read_file(std::string_view path) -> std::string {
    constexpr auto read_size = std::size_t{4096};
    auto stream = std::ifstream{path.data()};
    stream.exceptions(std::ios_base::badbit);

    auto out = std::string{};
    auto buf = std::string(read_size, '\0');
    while (stream.read(& buf[0], read_size)) {
        out.append(buf, 0, stream.gcount());
    }
    out.append(buf, 0, stream.gcount());
    return out;
}
person Konrad Rudolph    schedule 22.09.2008
comment
Jaki jest sens robienia z tego onelinera? Zawsze optowałbym za czytelnym kodem. Jako samozwańczy entuzjasta VB.Net (IIRC) myślę, że powinieneś zrozumieć ten sentyment? - person sehe; 21.09.2012
comment
@sehe: Spodziewałbym się, że każdy w połowie kompetentny programista C++ z łatwością zrozumie tę jednowierszową wypowiedź. Jest dość oswojony w porównaniu do innych rzeczy w okolicy. - person DevSolar; 21.09.2012
comment
@DevSolar Cóż, bardziej czytelna wersja jest o ~ 30% krótsza, brakuje jej obsady i poza tym jest równoważna. Dlatego moje pytanie brzmi: jaki jest sens robienia z tego onelinera? - person sehe; 21.09.2012
comment
@sehe Nigdy nie powiedziałem, że użyję onelinera w prawdziwym kodzie. Chodziło raczej o pokazanie, że można to zrobić jednym wyrażeniem. - person Konrad Rudolph; 24.09.2012
comment
@KonradRudolph Wokay. Miło wiedzieć, że. Jestem trochę zmieszany, że wtedy o tym wspomniałeś. W każdym razie zagłosuję na twoją inną odpowiedź (?!), a następnie za sztuczkę :) - person sehe; 24.09.2012
comment
Wiem, że to bardzo stare, ale właśnie wykonałem profilowanie kilku metod i odkryłem, że uzyskanie rozmiaru pliku i wywołanie in.read do bufora wstępnie przydzielonego do prawidłowego rozmiaru jest znacznie szybsze. Około 10x. Używam VS2012 i testuję z plikiem 100 MB. - person David; 14.05.2013
comment
@Dave Minimalnie szybciej – może. 10x? Wskazuje to na defekt w implementacji biblioteki standardowej. - person Konrad Rudolph; 14.05.2013
comment

To działa dla mnie! if grep -n '^ ' config.js /dev/null > następnie echo "$0: Wiodące spacje określone powyżej; przerywanie" >&2 wyjście 1 fi

- person Rahul Iyer; 13.10.2014
comment
@John Właśnie dlatego przypisałeś mu właściwą funkcję. Większość nietrywialnego kodu jest trudna do zrozumienia dla początkujących, gdyby to był argument przeciwko używaniu takiego kodu, nigdy byśmy nie wykonali żadnej pracy. - person Konrad Rudolph; 13.10.2014
comment
uwaga: ta metoda wczytuje plik do bufora stringstream, a następnie kopiuje cały bufor do string. Tj. wymagające dwa razy więcej pamięci niż niektóre inne opcje. (Nie ma możliwości przeniesienia bufora). W przypadku dużego pliku byłaby to znacząca kara, być może nawet powodująca błąd alokacji. - person M.M; 06.02.2016
comment
@M.M Słuszna uwaga, nie mam pojęcia, jak to mogło tak długo pozostać niezauważone. - person Konrad Rudolph; 06.02.2016
comment
@sehe Jeśli to jest warte, kładę ogromny nacisk na zwięzłość. Nie chcę wprowadzać nowej funkcji tylko ze względu na to, co w moim obecnym programie jest drobną funkcjonalnością polegającą na czytaniu jednej linii z pliku w nieistotnym celu. Sam wymóg dodania w tym celu funkcji spowodowałby, że nawet nie zawracałbym sobie głowy czytaniem tej linii. W moim przypadku posiadanie do tego jednej linii kodu pozwala na to, aby pojedyncza linia kodu nie wyróżniała się, więc robię to z radością! - person Dan Nissenbaum; 12.02.2016
comment
@DanNissenbaum Coś mylisz. Zwięzłość jest rzeczywiście ważna w programowaniu, ale właściwym sposobem na osiągnięcie tego jest rozbicie problemu na części i zamknięcie ich w niezależne jednostki (funkcje, klasy itp.). Dodawanie funkcji nie umniejsza zwięzłości; wręcz przeciwnie. - person Konrad Rudolph; 12.02.2016
comment
@KonradRudolph Słyszę cię. Z biegiem lat odszedłem od dodawania funkcji i klas do jednorazowego użytku, ponieważ sama ich obecność podkreśla ich znaczenie. Miło jest móc spojrzeć na kod i zobaczyć prosty, mały zestaw funkcji i klas reprezentujących podstawową funkcjonalność. Zacząłem stosować „zasadę trzech” – jeśli krótki blok kodu zostanie użyty tylko raz lub nawet dwa razy, korzyść braku funkcji może przeważyć nad korzyściami wynikającymi z enkapsulacji. Dopiero gdy osiągnie trzecie użycie, czasami będę skłonny go zamknąć. To „sorrpowanie plików” pasuje. - person Dan Nissenbaum; 12.02.2016
comment
@DanNissenbaum dlatego wprowadzono lambdy :) - person Ruslan; 29.06.2016
comment
Myślę, że to rozwiązanie działa tylko wtedy, gdy chcesz przeczytać plik w trybie binarnym. Jeśli chcesz przeczytać to w trybie tekstowym, istream_iterator jest najczystszym sposobem. Czy to jest poprawne? - person Maxpm; 22.05.2017
comment
Ten sposób jest powolny (ponieważ std::stringstream jest powolny). - person Galik; 16.07.2018
comment
@Galik Slow w porównaniu do czego? Wczytywanie strumienia ciągów znaków jest niezwykle szybkie. Problem polega na tym, że danych ciągu nie można przenieść ze strumienia, należy je skopiować. - person Konrad Rudolph; 16.07.2018
comment
Dlaczego nie dynamic_cast zamiast static_cast? Czy po prostu nie poniżamy? - person Ayxan Haqverdili; 03.03.2019
comment
@Ayxan Używanie dynamic_cast naprawdę ma sens tylko wtedy, gdy nie wiesz, czy rzutowanie się powiedzie, i przetestujesz wartość zwracaną (lub złapiesz potencjał bad_cast). Wiemy jednak, że obsada ma tutaj sukces, więc nie ma potrzeby zabezpieczać się. Idealnie byłoby użyć rzutu, który tylko wykonuje rzut w dół, a jednocześnie zapewnia, że ​​rzutowanie się powiedzie. Niestety, taka obsada nie istnieje w C++. - person Konrad Rudolph; 03.03.2019
comment
Czy ta metoda spowoduje wielokrotną realokację pamięci? - person coin cheung; 11.03.2020
comment
to rozwiązanie jest krótkie, ale mylące. rdbuf() zwraca filebuf*. W jaki sposób umieszczenie wskaźnika na rdbuf powoduje, że stringstream odczytuje zawartość pliku? Wolałbym bardziej szczegółowy, ale bardziej przejrzysty kod niż ta magia. - person anton_rh; 27.04.2020
comment
@anton_rh To nie jest magia, ale wymaga wiedzy, jak pracują odpowiedni członkowie, co jest udokumentowane. Wydaje się, że brakuje Ci przeciążenia (9) na tej stronie: en.cppreference .com/w/cpp/io/basic_ostream/operator_ltlt - person Konrad Rudolph; 27.04.2020

Najkrótszy wariant: Live On Coliru

std::string str(std::istreambuf_iterator<char>{ifs}, {});

Wymaga nagłówka <iterator>.

Pojawiły się raporty, że ta metoda jest wolniejsza niż wstępne przydzielanie łańcucha i używanie std::istream::read. Jednak w przypadku nowoczesnego kompilatora z włączoną optymalizacją wydaje się, że już tak nie jest, chociaż względna wydajność różnych metod wydaje się w dużym stopniu zależna od kompilatora.

person Konrad Rudolph    schedule 22.09.2008
comment
Czy mógłbyś rozwinąć tę odpowiedź. Jak skuteczne jest to rozwiązanie, czy czyta plik po znaku na raz, w każdym razie w celu wstępnego przydzielenia poruszającej się pamięci? - person Martin Beckett; 22.09.2008
comment
@M.M Sposób, w jaki czytam to porównanie, jest wolniejszy niż metoda wczytywania czystego C++ do wstępnie przydzielonego bufora. - person Konrad Rudolph; 06.02.2016
comment
Masz rację, jest to przypadek, w którym tytuł znajduje się pod próbką kodu, a nie nad nim :) - person M.M; 06.02.2016
comment
Czy ta metoda spowoduje wielokrotną realokację pamięci? - person coin cheung; 11.03.2020
comment
@coincheung Niestety tak. Jeśli chcesz uniknąć alokacji pamięci, musisz ręcznie buforować odczyt. Strumienie IO w C++ są dość kiepskie. - person Konrad Rudolph; 16.03.2020
comment
@KonradRudolph Dzięki, zauważyłem, że istnieje inny sposób: stringstream ss; ifs >> ss.rdbuf(); str = ss.str();, czy ta metoda spowoduje również wiele realokacji pamięci? - person coin cheung; 16.03.2020
comment
@coincheung To powinno unikać powtarzających się alokacji, ale w praktyce głupio tak nie jest. „Kanoniczny” sposób odczytania całego pliku w C++ 17 to Gist.github.com/klmr/ 849cbb0c6e872dff0fdcc54787a66103. Niestety bardzo rozwlekłe. - person Konrad Rudolph; 16.03.2020

Zobacz tę odpowiedź na podobny temat pytanie.

Dla Twojej wygody ponownie publikuję rozwiązanie CTT:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

To rozwiązanie zaowocowało o około 20% szybszym czasem wykonania niż inne przedstawione tutaj odpowiedzi, przy średniej 100 przebiegach względem tekstu Moby Dicka (1,3M). Nieźle jak na przenośne rozwiązanie C++, chciałbym zobaczyć wyniki mmapowania pliku ;)

person paxos1977    schedule 08.02.2009
comment
powiązane: porównanie wydajności czasowej różnych metod: Wczytywanie całego pliku na raz w C++ - person jfs; 03.12.2014
comment
Aż do dzisiaj nigdy nie widziałem, żeby funkcja tellg() raportowała wyniki inne niż rozmiar pliku. Znalezienie źródła błędu zajęło mi wiele godzin. Proszę nie używać funkcji tellg() do sprawdzania rozmiaru pliku. stackoverflow.com /pytania/22984956/ - person Puzomor Croatia; 27.12.2016
comment
nie powinieneś zadzwonić do ifs.seekg(0, ios::end) przed tellg? tuż po otwarciu pliku wskaźnik odczytu znajduje się na początku, więc tellg zwraca zero - person Andriy Tylychko; 09.02.2017
comment
musisz także sprawdzić, czy nie ma pustych plików, ponieważ usuniesz odwołanie do nullptr przez &bytes[0] - person Andriy Tylychko; 09.02.2017
comment
ok, przegapiłem ios::ate, więc myślę, że wersja z wyraźnym przejściem na koniec byłaby bardziej czytelna - person Andriy Tylychko; 09.02.2017
comment
Należy pamiętać, że to rozwiązanie działa tylko w trybie binarnym; mając na uwadze, że OP poprosił o rozwiązanie zarówno dla trybu binarnego, jak i tekstowego. - person M.M; 12.06.2018
comment
Ponieważ ciągi C++ 11 mają gwarancję ciągłego przechowywania, można więc bezpośrednio użyć ciągu zamiast wektora i w ten sposób pominąć wektor do kopiowania ciągu. - person syam; 18.12.2018
comment
To rozwiązanie nie jest przenośne, ponieważ w wyniku .tellg() nie ma gwarancji zwrócenia rozmiaru pliku. (a w praktyce niektóre systemy tego nie robią). - person spectras; 25.02.2021
comment
@spectras czy możesz wskazać system lub implementację C++, o której wiadomo, że nie zwraca przesunięcia w bajtach? - person paxos1977; 15.03.2021
comment
@paxos1977› określenie, w których systemach Twój program jest określony jako poprawny, zależy od Ciebie. W obecnej sytuacji opiera się na gwarancjach, których nie zapewnia C++, i jako taki jest błędny. Jeśli działa na znanym zestawie implementacji, które zapewniają takie gwarancje (jak w: udokumentowane jako gwarancje, a nie tylko tak się składa, że ​​dzisiaj wygląda dobrze w tej wersji, którą mam w pobliżu), to wyjaśnij to, w przeciwnym razie będzie to mylące. - person spectras; 16.03.2021
comment
@spectras Zgadzam się, że standard nie gwarantuje, że jest to przenośne dla wszystkich implementacji, b/c zależy to od zachowania specyficznego dla implementacji ... jednak jest zepsuty tylko w nieznanej, nienazwanej, teoretycznej implementacji C++, która używa tokenów zamiast przesunięcia bajtów dla tellg(). Nie możesz podać implementacji, w której to by nie zadziałało i ja też nie, więc myślę, że jest to wystarczająco przenośne. - person paxos1977; 18.03.2021
comment
Idealne uzasadnienie do tworzenia kruchych baz kodu, które nieoczekiwanie psują się, ponieważ jakiekolwiek zachowanie, które zaobserwowałem pewnego dnia, było wystarczająco przenośne. Dopóki ktoś tego nie zmienił. To nie jest tak, że mamy ciągłą historię. Prawidłową inżynierię przeprowadza się w oparciu o gwarancje, a nie sprawdzanie tego, co obecnie wydaje się działać i mieć nadzieję na najlepsze. Zatem: ten kod jest tylko solidną implementacją inżynieryjną, w której gwarantowane są jego założenia. [uwaga: nie mówiłem o tym, czy dzisiaj to zadziała, czy nie, to nie ma znaczenia] - person spectras; 18.03.2021
comment
…W przeciwnym razie nie jest to lepsze niż użycie po usunięciu lub wiszące odwołanie, ale nigdy nie uległo awarii w żadnym miejscu, w którym go uruchomiłem, więc jest wystarczająco przenośne. - person spectras; 18.03.2021
comment
@spectras Nie umieściłbym zachowania zdefiniowanego w implementacji w tej samej klasie błędów, co użycie po swobodnym lub zawieszonym odwołaniu. Użyj po swobodnych i zwisających odniesieniach, które są zawsze wszędzie zepsute. Stosujesz hiperbolę. - person paxos1977; 19.03.2021
comment
@spectras Właściwa inżynieria odbywa się poprzez oparcie się na gwarancjach, a nie na sprawdzaniu tego, co wydaje się teraz działać, i nadziei na najlepsze, jak student, który tak naprawdę nigdy nie pisał ani nie utrzymywał prawdziwej bazy kodu produkcyjnego. W prawdziwym świecie zawsze wiesz dokładnie, na jakie platformy celujesz i z jakimi kompilatorami. Jeśli w zależności od zdefiniowanego zachowania, uzyskasz lepszą wydajność, zrób to i zanotuj problem w dzienniku zatwierdzeń i komentarzach w kodzie. Jeśli wydajność nie ma znaczenia, zaimplementowałbyś użycie najbardziej czytelnego kodu, a nie najszybszego. - person paxos1977; 19.03.2021
comment
…lub mówił jako doświadczony profesjonalista, który widział wiele takich sytuacji. Błędy podczas pracy nad długotrwałymi bazami kodu dowiedzieli się, że to nigdy nie powinno się zdarzyć, nigdy nie będziemy skupiać się na innej platformie, kompilator nigdy nie otrzyma nowej wersji, nie znajdują się w prawdziwej bazie kodu produkcyjnego dla firmy, która przeżyła pierwszą premierę produktu. Kiedy więc zaczniesz na nich polegać, absolutnym minimum jest wyraźne oznaczenie tego i wykonanie testu jednostkowego. - person spectras; 19.03.2021

Jeśli masz C++ 17 (std::filesystem), istnieje również ten sposób (który pobiera rozmiar pliku przez std::filesystem::file_size zamiast seekg i tellg):

#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f(path, std::ios::in | std::ios::binary);

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, '\0');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}

Uwaga: może być konieczne użycie <experimental/filesystem> i std::experimental::filesystem, jeśli Twoja standardowa biblioteka nie obsługuje jeszcze w pełni C++ 17. Może być także konieczne zastąpienie result.data() przez &result[0], jeśli nie obsługuje ono non-const std ::basic_string danych.

person Gabriel Majeri    schedule 01.12.2016
comment
Może to powodować niezdefiniowane zachowanie; otwarcie pliku w trybie tekstowym daje inny strumień niż plik dyskowy w niektórych systemach operacyjnych. - person M.M; 12.06.2018
comment
Pierwotnie opracowany jako boost::filesystem, więc możesz także użyć wzmocnienia, jeśli nie masz C++ 17 - person Gerhard Burger; 29.09.2018
comment
Otwarcie pliku za pomocą jednego interfejsu API i uzyskanie jego rozmiaru za pomocą innego wydaje się wymagać niespójności i warunków wyścigu. - person Arthur Tacca; 24.10.2018

Używać

#include <iostream>
#include <sstream>
#include <fstream>

int main()
{
  std::ifstream input("file.txt");
  std::stringstream sstr;

  while(input >> sstr.rdbuf());

  std::cout << sstr.str() << std::endl;
}

lub coś bardzo blisko. Nie mam otwartego odniesienia do stdlib, aby samemu sprawdzić.

Tak, rozumiem, że nie napisałem funkcji slurp zgodnie z pytaniem.

person Ben Collins    schedule 22.09.2008
comment
Wygląda to ładnie, ale się nie kompiluje. Zmiany powodujące kompilację redukują je do innych odpowiedzi na tej stronie. ideone.com/EyhfWm - person JDiMatteo; 29.06.2015
comment
Dlaczego pętla while? - person Zitrax; 19.10.2017
comment
Zgoda. Kiedy operator>> wczytuje do std::basic_streambuf, pochłonie (to, co z niego zostało) strumień wejściowy, więc pętla jest niepotrzebna. - person Remy Lebeau; 30.12.2018

Nie mam wystarczającej reputacji, aby bezpośrednio komentować odpowiedzi za pomocą tellg().

Należy pamiętać, że tellg() może zwrócić -1 w przypadku błędu. Jeśli przekazujesz wynik tellg() jako parametr alokacji, powinieneś najpierw sprawdzić wynik.

Przykład problemu:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

W powyższym przykładzie, jeśli tellg() napotka błąd, zwróci -1. Niejawne rzutowanie pomiędzy znakiem (tj. wynikiem tellg()) i unsigned (tj. argumentem do konstruktora vector<char>) spowoduje, że wektor błędnie przydzieli bardzo dużą liczbę bajtów. (Prawdopodobnie 4294967295 bajtów, czyli 4 GB.)

Modyfikowanie odpowiedzi paxos1977 w celu uwzględnienia powyższego:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}
person Rick Ramstetter    schedule 24.03.2017
comment
Co więcej, tellg() nie zwraca rozmiaru, ale token. Wiele systemów używa przesunięcia bajtów jako tokenu, ale nie jest to gwarantowane, a niektóre systemy tego nie robią. Sprawdź tę odpowiedź, aby zobaczyć przykład. - person spectras; 25.02.2021

To rozwiązanie dodaje sprawdzanie błędów do metody opartej na rdbuf().

std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}

Dodaję tę odpowiedź, ponieważ dodanie sprawdzania błędów do oryginalnej metody nie jest tak trywialne, jak można by się spodziewać. Oryginalna metoda wykorzystuje operator wstawiania stringstream (str_stream << file_stream.rdbuf()). Problem polega na tym, że ustawia to bit błędu strumienia stringstream, gdy nie są wstawiane żadne znaki. Może to być spowodowane błędem lub pustym plikiem. Jeśli sprawdzasz błędy, sprawdzając bit błędu, podczas czytania pustego pliku napotkasz fałszywy alarm. Jak ujednoznacznić uzasadniony brak wstawienia jakichkolwiek znaków i „niepowodzenie” wstawienia jakichkolwiek znaków, ponieważ plik jest pusty?

Możesz pomyśleć o jawnym sprawdzeniu pustego pliku, ale to więcej kodu i powiązanego sprawdzania błędów.

Sprawdzanie warunku niepowodzenia str_stream.fail() && !str_stream.eof() nie działa, ponieważ operacja wstawiania nie ustawia eofbitu (w strumieniu ostring ani w strumieniu if).

Rozwiązaniem jest zatem zmiana operacji. Zamiast używać operatora wstawiania ostringstream (‹‹), użyj operatora wyodrębniania ifstream (>>), który ustawia eofbit. Następnie sprawdź warunek awarii file_stream.fail() && !file_stream.eof().

Co ważne, gdy file_stream >> str_stream.rdbuf() napotka uzasadnioną awarię, nie powinien nigdy ustawiać eofbitu (zgodnie z moim zrozumieniem specyfikacji). Oznacza to, że powyższa kontrola jest wystarczająca do wykrycia uzasadnionych błędów.

person tgnottingham    schedule 26.03.2017

Coś takiego nie powinno być takie złe:

void slurp(std::string& data, const std::string& filename, bool is_binary)
{
    std::ios_base::openmode openmode = ios::ate | ios::in;
    if (is_binary)
        openmode |= ios::binary;
    ifstream file(filename.c_str(), openmode);
    data.clear();
    data.reserve(file.tellg());
    file.seekg(0, ios::beg);
    data.append(istreambuf_iterator<char>(file.rdbuf()), 
                istreambuf_iterator<char>());
}

Zaletą jest to, że najpierw robimy rezerwę, więc nie będziemy musieli powiększać ciągu podczas wczytywania. Wadą jest to, że robimy to znak po znaku. Inteligentniejsza wersja mogłaby pobrać cały bufor odczytu, a następnie wywołać niedopełnienie.

person Matt Price    schedule 22.09.2008
comment
Powinieneś sprawdzić wersję tego kodu, która używa std::vector do początkowego odczytu, a nie ciągu znaków. Dużo, dużo szybciej. - person paxos1977; 08.02.2009

Oto wersja korzystająca z nowej biblioteki systemu plików z dość solidną kontrolą błędów:

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };
person David G    schedule 06.11.2019
comment
infile.open może również zaakceptować std::string bez konwersji za pomocą .c_str() - person Matt Eding; 02.01.2020
comment
filepath to nie std::string, to std::filesystem::path. Okazuje się, że std::ifstream::open również może zaakceptować jedno z nich. - person David G; 10.01.2020
comment
@DavidG, std::filesystem::path można domyślnie przekształcić w std::string - person Jeffrey Cash; 07.02.2020
comment
Według cppreference.com funkcja członkowska ::open na std::ifstream, która akceptuje std::filesystem::path, działa tak, jakby na ścieżce została wywołana metoda ::c_str(). Podstawowe ::value_type ścieżek to char w standardzie POSIX. - person David G; 18.02.2020

Ponieważ wydaje się to powszechnie używanym narzędziem, moje podejście polegałoby na wyszukiwaniu i preferowaniu już dostępnych bibliotek od ręcznie stworzonych rozwiązań, zwłaszcza jeśli biblioteki wspomagające są już połączone (flagi linkera -lboost_system -lboost_filesystem) w twoim projekcie. Tutaj (i starsze wersje boost), boost zapewnia narzędzie loading_string_file:

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>

int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

Zaletą tej funkcji jest to, że nie wyszukuje całego pliku w celu określenia rozmiaru, zamiast tego używa wewnętrznie funkcji stat(). Jednak jako prawdopodobnie nieistotną wadę można łatwo wywnioskować po sprawdzeniu kodu źródłowego: rozmiar łańcucha jest niepotrzebnie zmieniany za pomocą znaku '\0', który jest przepisywany przez zawartość pliku.

person b.g.    schedule 11.09.2020

Możesz użyć funkcji „std::getline” i określić „eof” jako ogranicznik. Wynikowy kod jest jednak trochę niejasny:

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );
person Martin Cote    schedule 22.09.2008
comment
Właśnie to przetestowałem, wydaje się, że jest to znacznie wolniejsze niż pobieranie rozmiaru pliku i wywoływanie odczytu całego rozmiaru pliku do bufora. Około 12 razy wolniej. - person David; 14.05.2013
comment
To zadziała tylko wtedy, gdy w twoim pliku nie będzie znaków eof (np. 0x00, 0xff, ...). Jeśli tak, przeczytasz tylko część pliku. - person Olaf Dietsche; 12.08.2017

#include <string>
#include <sstream>

using namespace std;

string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}

string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }

    return GetStreamAsString(stream);
}

stosowanie:

const string logAsString = GetFileAsString(logFilePath);
person Paul Sumpner    schedule 17.09.2019

Zaktualizowana funkcja oparta na rozwiązaniu CTT:

#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
{
    std::ios::openmode openmode = std::ios::in;
    if(binaryMode)
    {
        openmode |= std::ios::binary;
    }
    std::ifstream ifs(path.data(), openmode);
    ifs.ignore(std::numeric_limits<std::streamsize>::max());
    std::string data(ifs.gcount(), 0);
    ifs.seekg(0);
    ifs.read(data.data(), data.size());
    return data;
}

Istnieją dwie istotne różnice:

tellg() nie gwarantuje zwrócenia przesunięcia w bajtach od początku pliku. Zamiast tego, jak zauważył Puzomor Croatia, jest to raczej token, którego można używać w wywołaniach fstream. gcount() jednakże zwraca ilość ostatnio wyodrębnionych niesformatowanych bajtów. Dlatego otwieramy plik, wyodrębniamy i odrzucamy całą jego zawartość za pomocą ignore(), aby uzyskać rozmiar pliku, i na tej podstawie konstruujemy ciąg wyjściowy.

Po drugie, unikamy konieczności kopiowania danych pliku z std::vector<char> do std::string, zapisując bezpośrednio do ciągu.

Pod względem wydajności powinno to być absolutnie najszybsze rozwiązanie, przydzielając z wyprzedzeniem ciąg znaków o odpowiednim rozmiarze i jednokrotnie wywołując read(). Co ciekawe, użycie ignore() i countg() zamiast ate i tellg() na gcc kompiluje do prawie tego samego kawałek po kawałku.

person kiroma    schedule 04.05.2020
comment
Ten kod nie działa, otrzymuję pusty ciąg. Myślę, że chciałeś ifs.seekg(0) zamiast ifs.clear() (wtedy to działa). - person Xeverous; 15.07.2020

Nigdy nie zapisuj do bufora const char * std::string. Nigdy przenigdy! Takie postępowanie jest ogromnym błędem.

Zarezerwuj() miejsce na cały ciąg w std::string, przeczytaj fragmenty pliku o rozsądnym rozmiarze do bufora i dodaj je(). Wielkość porcji zależy od rozmiaru pliku wejściowego. Jestem prawie pewien, że wszystkie inne mechanizmy przenośne i zgodne z STL zrobią to samo (choć mogą wyglądać ładniej).

person Thorsten79    schedule 22.09.2008
comment
Ponieważ C++ 11 gwarantuje, że zapis bezpośrednio do bufora std::string będzie OK; i uważam, że działało to poprawnie we wszystkich wcześniejszych implementacjach - person M.M; 16.07.2018
comment
Od C++17 mamy nawet niestałą metodę std::string::data() dla modyfikowanie bufora ciągów bezpośrednio bez uciekania się do sztuczek takich jak &str[0]. - person zett42; 16.11.2018
comment
Zgadzam się z @ zett42, ta odpowiedź jest niepoprawna pod względem faktycznym - person jeremyong; 16.03.2019

person    schedule
comment
Proszę dodać opis. - person Piotr Noga; 13.06.2020
comment
odwiedź i sprawdź, jak odpowiedzieć na pytanie. - person Yunus Temurlenk; 13.06.2020