Дан текстовый файл

Ребята кто прошаренный в С++ помогите пожалуйста. С чего начать? что делать?
1. Дан текстовый файл, нужно информацию из него перезаписать в обратном порядке в новый файл.
2. Дан текстовый файл, нужно информацию из него перезаписать в новый файл только цифры.

//g++  5.4.0
//C++11 needed

#include <sstream>
#include <fstream>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <algorithm>



template<typename FileStreamType>
FileStreamType open_file(const std::string &in_path)
{
    FileStreamType file(in_path);
    if (!file.is_open()) {
        throw std::runtime_error(std::string("file not open \"") + in_path + "\"");
    }  
    return std::move(file);
}

template<typename FileStreamType>
std::string read_all_from_stream(FileStreamType &&stream)
{
    std::stringstream ss;
    ss.clear(); ss.str("");
    ss << stream.rdbuf();
   return ss.str();
}


std::string file_open_and_read(const std::string &in_path)
{
    return read_all_from_stream(open_file<std::ifstream>(in_path));
}


void file_reverse_copy(const std::string &in_path, const std::string &out_path)
{
    std::string content = file_open_and_read(in_path);
    auto out = open_file<std::ofstream>(out_path);
    std::reverse_copy(content.begin(), content.end(), std::ostream_iterator<char>(out));
}



void file_copy_digits(const std::string &in_path, const std::string &out_path)
{
    std::string content = file_open_and_read(in_path);
    auto out = open_file<std::ofstream>(out_path);
    std::copy_if(content.begin(), content.end(), std::ostream_iterator<char>(out), ::isdigit);
}


int main()
{
    try {
        file_reverse_copy(__FILE__, "reverse.txt");
        file_copy_digits(__FILE__, "digits.txt");
    } catch (const std::exception &e) {
        std::cerr << e.what() << "\n";
    }
}

Круто! )) Мне даже показалось, что слишком круто...

Сначала то, что не понял:

  1. Зачем template в read_all_from_stream? Мне кажется, что вполне хватило бы std::string read_all_from_stream(istream &&stream). Не?

  2. Зачем в переметрах функций используется const std::string &, когда хватило бы const char *? Вроде проще передать указатель на С-строку, чем сначала сконструировать из С-строк std::string, а потом уже передавать адреса при вызове функций.

Теперь, что показало тестирование.

Win 10 x64 ver. 1703, MS Visual Studio Community 2017. (Подозреваю, что сейчас в меня полетят гнилые яблоки, но тем не менее, компилятор С++ от MS — промышленный стандарт для разработки под Windows. И другого компилятора все равно под рукой нет.)

  1. В Debug-конфигурации программа падает по assert'у, если в файле есть кириллица. В моем случае, никакого юникода, всё в cp1251. В Release — работает. Лечится заменой ::isdigit на [](char a) { return a >= '0' && a <= '9'; }. Пробовал setlocale(LC_ALL, "Russian") — не помогает.

  2. Попробовал в качестве входного файла подсунуть текстовый файл размером 1Gb. Если компилить x64, то работает. При этом занимает 984580К оперативы. Если компилить в x86, то после запуска программа очень долго думает (несколько десятков минут; точно время не засекал), занимая 691148К памяти, пототм выдает bad allocation и умирает.

Мне кажется, всасывать в std::string весь файл целиком — не лучшая идея.

Мне кажется, что вполне хватило бы

Конкретно для этого примера хватило бы. А вообще код переделан из другого, поэтому там еще вызовы clear и str остались.

Вроде проще передать указатель на С-строку, чем сначала сконструировать из С-строк std::string, а потом уже передавать адреса при вызове функций.

Если путь собирается из частей, то мороки с указателями больше. А что касается данного примера, то всё равно не будет аллокаций памяти для std::string.

В Debug-конфигурации программа падает по assert'у, если в файле есть кириллица. В моем случае, никакого юникода, всё в cp1251. В Release — работает.

Этот assert на cl достал в своё время. Он считает, что при использовании char код символа не должен быть больше 127 (то бишь меньше нуля). В Release этот assert выпиливается. И программа не падает, там есть кнопочка «Продолжить». Это типа нас так предупреждают.

Пробовал setlocale(LC_ALL, «Russian») — не помогает.

И не поможет. Скорее всего оно еще и с кириллаческими путями работать не будет, потому что для винды придется конвертировать в wchar_t. )))

Попробовал в качестве входного файла подсунуть текстовый файл размером 1Gb. Если компилить x64, то работает. При этом занимает 984580К оперативы.

В данном случае нам нужно поместить в std::string весь этот гигабайт, что мы успешно и делаем. Работа с большими файлами требует другой организации чтения и записи, при этом код усложниться.

то после запуска программа очень долго думает

Потому что винда любит свой swap-файл до изнеможения задрочить. )))

Мне кажется, всасывать в std::string весь файл целиком — не лучшая идея.

Для мелких файлов — отлично, для больших алгоритм нужно изменять, причем существенно. Но файл текстовый, много гигабайт такие файлы, обычно, занимают, только если это таблицы для brootforce'а )))

Этот assert на cl достал в своё время. Он считает, что при использовании char код символа не должен быть больше 127 (то бишь меньше нуля)

Там в сообщении написано что-то типа c > -1 && c <= 255. Т.е. по-видимому, задействовано преобразование char -> int, при котором происходит расширение знака.

Потому что винда любит свой swap-файл до изнеможения задрочить. )))

Причём здесь своп? 32-разрядная версия тупо не может выделить память под буфер строки «слишком» большого размера. Непонятно только почему она не вываливается после первой же неудачи. Исключение там генерируется, судя по логам VS, но видимо где-то там, во глубине, и перехватывается. Вообще мне кажется, что то исключение, которое перехватывает catch в main, это ошибка распределения памяти при генерации множественных объектов исключений в new.

память под буфер строки

Точнее, память под буфер stringstream.

Там в сообщении написано что-то типа c > -1 && c <= 255. Т.е. по-видимому, задействовано преобразование char -> int, при котором происходит расширение знака.

Именно. Знаковый char расширяется, но проблема в том, что они же знают, что он знаковый и что будет так расширяться. А значит при любом значении char более 127 (менее 0), получим такой assert. )))

Причём здесь своп? 32-разрядная версия тупо не может выделить память под буфер строки «слишком» большого размера.

Ей 2Gb положено. Если нету — bad_alloc (под линуксом не всегда, под виндой не знаю), а вот тупить она начнет, когда будет задействован swap. Хотя понятие «тупить» у каждого разное.

Знаковый char расширяется, но проблема в том, что они же знают, что он знаковый и что будет так расширяться.

Это ж американцы!.. Они не знают дальше ASCII7 ((

Ей 2Gb положено.

  1. В своп она не лезет. Нет обмена с диском.

  2. А вот на счёт 2Gb — здесь вот ворос интересный. 2Gb чего? Код + стек + куча? У меня программа занимает памяти 691Mb, когда начинаются тормоза. Выше этого объем занимаемой памяти не поднимается (по Task Manager). Если не отжирать такой буфер, то программа занимает порядка 450Kb (~0.5Mb) памяти. А если сделать логгирование new/delete (перегрузить стандартные new и delete), то получается следующее:

new(32) -> 00568DA0
new(64) -> 005666D8
delete(00568DA0)
new(96) -> 0056F0A0
delete(005666D8)
new(144) -> 00567298
delete(0056F0A0)
new(216) -> 00570640
delete(00567298)
new(324) -> 00570720
delete(00570640)
new(486) -> 00570870
delete(00570720)
new(729) -> 00570A60
delete(00570870)
new(1093) -> 00571FF0
delete(00570A60)
new(1639) -> 00570640
delete(00571FF0)
new(2458) -> 00571FF0
delete(00570640)
new(3687) -> 00572998
delete(00571FF0)
new(5565) -> 00573808
delete(00572998)
new(8330) -> 00574DD0
delete(00573808)
new(12477) -> 00576E68
delete(00574DD0)
new(18698) -> 00571FF0
delete(00576E68)
new(28029) -> 00576908
delete(00571FF0)
new(42026) -> 0057D690
delete(00576908)
new(63021) -> 00587AC8
delete(0057D690)
new(94514) -> 00597100
delete(00587AC8)
new(141753) -> 00571FF0
delete(00597100)
new(212612) -> 005949B8
delete(00571FF0)
new(318900) -> 005C8848
delete(005949B8)
new(478332) -> 00B40048
delete(005C8848)
new(717480) -> 00C47020
delete(00B40048)
new(1076202) -> 00D0F020
delete(00C47020)
new(1614285) -> 00E2C020
delete(00D0F020)
new(2421410) -> 00FC9020
delete(00E2C020)
new(3632097) -> 00C46020
delete(00FC9020)
new(5448128) -> 00FC8020
delete(00C46020)
new(8172174) -> 0150B020
delete(00FC8020)
new(12258243) -> 01CE2020
delete(0150B020)
new(18387347) -> 028AC020
delete(01CE2020)
new(27581003) -> 00C43020
delete(028AC020)
new(41371487) -> 026AB020
delete(00C43020)
new(62057213) -> 04E33020
delete(026AB020)
new(93085802) -> 08977020
delete(04E33020)
new(139628685) -> 0F39B020
delete(08977020)
new(209443010) -> 00C47020
delete(0F39B020)
new(314164497) -> 0F397020
delete(00C47020)
new(471246728) -> 21F43020
delete(0F397020)
new(706870074) -> 3E0B9020
delete(21F43020)
new(1060305093) -> 00000000

Здесь
new(размер_запрвшиваемой_памяти) -> адрес_выделенной_памяти
и
delete(адрес_освобождаемого_блока).

Кстати, в случае перегруженных new/delete (самая примитивная реализация через malloc/free) исключение появляется достаточно быстро, но почему-то не ловится по catch, а просто крашит приложение.

Как видно, последний успешный new получил 674Mb, а следующий запросил 1011Mb и обломился. Видимо стандартный new при невозможности выделить нужный кусок памяти запускает процедуру дефрагментации кучи, что выливается сначала в дикий тормоз, а потом в bad_alloc.

В своп она не лезет. Нет обмена с диском.

Это тогда хорошо.

меня программа занимает памяти 691Mb, когда начинаются тормоза.

Далее, например, добавляем что-то в стрингу, лезем за гигом памяти. Нужен такой непрерывный участок для строки. Попробуй найди. Менеджер памяти в шоке, но работать надо. Именно по этому мне не всегда подходят стандартные контейнеры — слишком большой размах расширения. От 1.6 до 2 раз хорошо подходит для мелких участков, а для больших объемов этого уже много. Например, у нас вектор на гиг, коэффициент расширения 1.6. Это значит, что для следующего расширения ему нужно еще 1.6 Гб памяти. Итого для вставки реаллокации потребуется держать занятой 2.6 Гб.

Видимо стандартный new при невозможности выделить нужный кусок памяти запускает процедуру дефрагментации кучи

Стандартный оператор new — нет. Такими манипуляциями занимается менеджер памяти системы, которому надо найти огромный непрерывный кусок памяти (упрощенно обзовём это дело именно так).

а потом в bad_alloc.

Для справки, оператор new может и не выкинуть исключение в случае неудачи. Причем он сам не в состоянии отследить неудачное выделение.

Кстати, в случае перегруженных new/delete

Стандартные операторы new и delete могут быть, и, скорее всего, будут устроены значительно сложнее.

Скучно здесь и не интересно. :(

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.