- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
#include "IO/FileWriter.h"
#include "IO/FileReader.h"
#include "IO/FileSystem.h"
#include "IO/FormattedWriter.h"
#include "Range/Generators/Recurrence.h"
#include "Range/Decorators/TakeByLine.h"
#include "Range/Decorators/Map.h"
#include "Platform/Endianess.h"
using namespace Intra;
using namespace IO;
using namespace Range;
void TestFileSyncIO(FormattedWriter& output)
{
//Открываем файл для записи как временный объект и сразу же записываем в него текст.
OS.FileOpenOverwrite("TestFileSyncIO.txt")
.PrintLine("Fibonacci sequence: ", Take(Recurrence([](int a, int b) {return a+b;}, 1, 1), 10))
.PrintLine("Closing file.");
//Здесь временный объект удаляется и файл закрывается сам.
//Файл можно присвоить строчке, потому что возвращаемый тип FileReader является диапазоном символов,
//а мои контейнеры умеют инициализироваться от любых диапазонов соответствующих типов элементов, беря все их элементы.
//В этом случае файл читается целиком в строку и закрывается, так как его временный объект удаляется.
String fileContents = OS.FileOpen("TestFileSyncIO.txt");
output.PrintLine("Written file contents:")
.PrintLine(fileContents);
INTRA_ASSERT_EQUALS(
TakeByLine(fileContents).First(), //TakeByLine не выделяет память, а возвращает StringView из уже существующей строчки
"Fibonacci sequence: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]");
INTRA_ASSERT_EQUALS(
ToString(Map(ByLine(fileContents), &String::Length)), //ByLine сохраняет каждую строку в String, для них возвращается длина и этот диапазон переводится в строку. Все эти действия происходят внутри ToString
"[54, 13]");
INTRA_ASSERT_EQUALS(
ToString(Map(OS.FileOpen("TestFileSyncIO.txt").ByLine(), &String::Length)), //Аналогично предыдущему, но строки берутся напрямую из файла и тоже внутри ToString
"[54, 13]");
//Чтобы не выделять память для строк, можно предоставить внешний буфер.
char buf[100];
INTRA_ASSERT_EQUALS(
ToString(Map(OS.FileOpen("TestFileSyncIO.txt").ByLine(buf), &StringView::Length)),
"[54, 13]");
//Если размера внешнего буфера не хватает, строка разбивается на несколько частей, ограниченных размером буфера.
char smallBuf[10];
INTRA_ASSERT_EQUALS(
ToString(Map(OS.FileOpen("TestFileSyncIO.txt").ByLine(smallBuf), &StringView::Length)),
"[10, 10, 10, 10, 10, 4, 10, 3]");
//Всё это счастье работает с циклом range-based for:
size_t sumLength = 0;
for(auto str: OS.FileOpen("TestFileSyncIO.txt").ByLine(buf))
sumLength += str.Length();
INTRA_ASSERT_EQUALS(sumLength, 67);
//При желании можно сохранять окончания строк:
sumLength = 0;
for(auto str: OS.FileOpen("TestFileSyncIO.txt").ByLine(buf, Tags::KeepTerminator))
sumLength += str.Length();
INTRA_ASSERT_EQUALS(sumLength, 71);
//Ну и конечно файл можно открыть по-нормальному и читать из него, в том числе и бинарные данные:
FileReader file = OS.FileOpen("TestFileSyncIO.txt");
uint value = file.ReadRaw<uintLE>(); //Читаем беззнаковое число в порядке байт little-endian
INTRA_ASSERT_EQUALS(char(value & 255), 'F');
INTRA_ASSERT_EQUALS(char((value >> 8) & 255), 'i');
INTRA_ASSERT_EQUALS(char((value >> 16) & 255), 'b');
INTRA_ASSERT_EQUALS(char((value >> 24) & 255), 'o');
value = OS.FileOpen("TestFileSyncIO.txt").ReadRaw<uintBE>(); //Читаем беззнаковое число в порядке байт big-endian
INTRA_ASSERT_EQUALS(char(value & 255), 'o');
INTRA_ASSERT_EQUALS(char((value >> 8) & 255), 'b');
INTRA_ASSERT_EQUALS(char((value >> 16) & 255), 'i');
INTRA_ASSERT_EQUALS(char((value >> 24) & 255), 'F');
//Можно даже копировать поток и работать с двумя потоками независимо. При этом на уровне ОС по-прежнему открыт только один файл.
FileReader file2 = file;
uint value1 = file.ReadRaw<uintLE>();
uint value2 = file2.ReadRaw<uintLE>();
INTRA_ASSERT_EQUALS(value1, value2);
}
Продолжаю делать свою убийцу стандартной библиотеки C++. Вот пример её использования.
Те, кому кажется, что C++ громоздкий и невыразительный, просто не умеют его готовить.
Данный код никоим образом не показывает негромоздкость и выразительность C++. И готовить его ты точно не умеешь.
А по мне. просто берешь cs2cpp и понвертишь mscorelib в с++ и выкидывешь мусор от тудава. И будет счастье
Всей жизни не хватит
Как это связано со стандартной библиотекой? Чем ужасен = 0?
Как это относится к = 0? Причём здесь стандартизация С++?
Ничего ужасного в = 0 не вижу, это гораздо лучше ключевого слова pure, которое по отношению к функциям означает совсем другое.
А потом добавили внезапные =delete и =default...
Ждём =new вместо virtual, =if вместо enable if, =for для генереции метушни, =switch для паттерн-матчинга и =auto чтобы конпелятор сам что-нибудь подходящее выбрал.
в языке есть конструкция, где функция чему-то приравнивается в объявлении, есть ключевые слова delete и default, хм, какие же конструкции языка придумает комитет чтобы реализовать запрет использования и реализацию функции по умолчанию?
=42 -- вывод кода функции на основании сигнатуры.
=throw -- ничего не делает, но при этом не даёт определить такой метод.
=1997 -- все перегруженные методы будут заанроллены, и запрещает использование в них плавающего питуха.
так есть же [[deprecated]]
STL не поддерживает копирование потоков. Приходится передавать их по ссылке. А что если функция споткнётся на ошибке в файле? Тогда позиция в потоке станет неопределённой. Она не может откатить его назад и не во всех случаях знает, где конец нужного куска.
А ещё файлы STL плохо отображаются в отладчике. Никакой полезной информации там нет. А мой FileReader в отладчике студии отображает даже ещё не прочитанное содержимое файла, текущую позицию в нём и его размер, что очень помогает в отладке.
Даже мой аналог std::vector лучше отображается в отладчике студии - можно видеть первые несколько элементов, не раскрывая содержимое объекта.
А ещё стандартная библиотека не предоставляет возможности быстро переводить число в строку и наоборот. Все её возможности даже медленнее sprintf, а моя либа делает это быстрее sprintf в 2 раза и при этом намного удобнее и безопаснее. А контейнеры в строку стандартная библиотека в принципе переводить не умеет. А моя умеет, причём в любом формате и работает даже с контейнерами STL, не подозревая даже об их существовании.
Потому что далеко не все потоки копируются?
Что происходит при копировании потока сокета? Ведь с прикладной точки зрения состояние для чтения зависит от того, что мы в него записали. Если код, который что-то читает из сокета, сломался, нормально продолжить с ним работу почти невозможно. Даже в posix почему-то отдельный lseek() для dup()нутых дескрипторов не осилили.
Другой InputStream и сокет можно только перемещать.
Оборачивай в std::shared_ptr
> А мой FileReader в отладчике студии ...
авторы stl клали большой и толстый на отладчик студии. У них не один компилятор, не одна целевая платформа и не одна ide, о которых надо помнить
Авторы реализации STL для студии должны были сделать её удобной для использования именно в студии.
И в принципе законы логики в разных IDE/компиляторах не меняются. Меняется лишь способ настройки отладчика (natvis, python для gdb и т.п.), чтобы он выводил то, что надо. Когда я разберусь с визуализацией в gdb, я и её сделаю для своей либы. И это покроет почти все основные компиляторы и все платформы. Сама по себе библиотека уже компилируется всеми основными компиляторами под винду, линукс и андроид.
Переменная x будет иметь тип RTake<FileReader>, который говорит, что надо взять столько-то символов из файла. И output.PrintLine прочитает эту строчку из файла. Несколько раз читать одно и то же из файла не очень хорошо в плане производительности, поэтому вместо TakeByLine лучше использовать просто ByLine. Тогда в x будет String.
Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
И да, функции не должны принимать имя файла, если только они зачем-то не хотят его запомнить. Они дожны принимать InputStream и работать с любыми потоками, а не только с файлами.
Т.е. вот этот код интерпретирует файлы, которые не получилось открыть, как пустые файлы? Удобно: зачем париться по поводу ошибок, если можно их игнорировать. Главное, чтобы код красивый был.
Если произошла ошибка и объект status не был проверен, то деструктор FatalErrorStatus крашнет программу с сообщением о том, что файл "TestFileSyncIO.txt" не удалось открыть по такой-то причине. Если ошибку нужно молча залогировать, но не крашиться, вместо FatalErrorStatus надо создать другой ErrorStatus с ассоциированным логом.
То есть случайно ошибку заигнорить не получится, потому что компилятор потребует передать аргумент. Но при желании ошибку легко явно заигнорить передачей специального игнорящего объекта ошибки, когда пустые файлы - то что надо. Если игнорить нельзя, можно завести FatalErrorStatus. Код страшнее от этого особо не становится и все ошибки обрабатываются явно - никаких неожиданных исключений.
Компилятор не позволит ошибкам распространяться неконтролируемо. Ошибку нужно передавать явно. При желании некритичную ошибку легко заигнорить и получить логичное поведение по умолчанию. Не нужно писать громоздкие try-catch и каждый раз реализовывать это поведение самому. А если забыть try-catch в некритичном месте, можно получить креш вместо по прежнему работающей программы.
1. позитивный и негативный сценарий обработки полностью обособленны. Скажем, пять вызовов, каждый из которых потенциально вызывает ошибку, можно обработать в одном месте
2. исключения строго типизированы и ловятся по типу. Это позволяет, например, на ошибку ввода реагировать всплывающим окошком, а фатальную ошибку ловить аж в main.
3. не пойманное исключение невозможно не заметить. А текст ошибки будет виден даже в релиз-билде.
4. Бывают сценарии, где ошибки крайне маловероятны. Скажем, система вряд ли запретит вам открыть только что этим же приложением созданный/скачанный файл.
А если переживаете за неявность выброшенного исключения - помечайте функции noexcept(false). Радикально, конечно, но двусмысленность исключена
Это сказка, которую постоянно пытаются продать. На самом деле если делать нормальную обработку ошибок, то чаще всего как раз понять, какая именно строчка кинула ошибку, и тут начинаются такие костыли/кубометры try/catch, что уж лучше бы явные объекты ошибок передавать.
Во-первых, если у меня пять запросов к устройству, мне необязательно явно указывать, какой именно из них вылетел из-за отсутствия соединения (потому что 99.9999% что первый, а даже если и не он, то какая разница?). Во-вторых, скажем, вряд ли ошибка "не могу скопировать файл" вылетит в строчке, которая его открывает. По факту, если у вас разные вызовы кидают одинаковые исключения, которые надо по разному обрабатывать - это проблема архитектуры приложения, а не технологии.
Надеюсь, это зелёным. Я ведь хочу не постфактум из лога узнать, почему произошла ошибка, а в приложении её обработать.
> какой именно из них вылетел из-за отсутствия соединения
Это зависит от стратегии обработки ошибок. Иногда вывести "устройство не работает :(" и доблестно умереть недостаточно. Я довольно долго работал над сервисом, где в случае разрыва соединения лучше всего не падать, а подождать восстановления соединения и продолжить выполнение с того места, где всё ещё было нормально.
В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.
Так ты ловишь какое-нибудь NonCriticalException исключение по типу, а потом обрабатываешь. А если поймал FatalException, то доблестно умираешь.
> В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.
Зависит от того, как используется асинхронность. asio с его callback'ами - там не столько сложно (всё равно есть "линейные" участки), сколько не нужно. А ежели асинхронность на фьючерсах/промисах, то ничто не мешает использовать исключения. std::error_code и std::system_error покрывают очень большой набор сценариев обработки ошибок
2) Как-то мне не приходилось сталкиваться с несколькими разными ошибками сразу. Для обработки достаточно информации о том, успешна операция или нет, а для изучения причины есть подробная информация в логе. И ошибки обрабатываю до того, как станет несколько ошибок, которые нужно различать.
3) У меня в конце п. 1 написано про то же самое. Только плюс к этому про ошибку в принципе нельзя забыть. Если функция может сфейлиться, она будет ожидать ссылку на объект ошибки, а не молча кидать исключения.
4) А причём здесь достоинства исключений?
Даже если я помечу noexcept(false), я могу забыть обратить на него внимание, потому что компилятор не будет меня принуждать писать обработчик или что-то куда-то передавать.
Выставил тебе заслуженный минус.
Получается естественная раскрутка стека без сюрпризов. И можно не беспокоиться за утечки не-RAII ресурсов, которые могут возникнуть при неожиданном вылете исключения.
У меня в либе подобная, только чуть более упрощённая, схема уже больше года работает и я ни разу не пожалел. Всё мега-удобно.
Это как с const, RAII и другими фишккми, которые помогают не допустить ошибку: компилятор не даст случайно изменить константный объект, и не надо самому освобождать ресурсы, потому что об этом позаботится деструктор.
Не совсем понятно, что должно происходить, когда один и тот же status просовывается в несколько вызовов, вот в коде вроде такого:
Какой из двух статусов должен выжить, если ошибка вдруг происходит в двух местах (прогу запустили в корне FS, например)?
ФЛАГ_БАНК - "не могу открыть файл", "файла не существует" и "открылся существующий файл, но он пустой" - разные ситуации
ФЛАГ_СТД - "файла не существует" и "открылся существующий файл, но он пустой" эквивалентны
ФЛАГ_СКРИПТ - все эти ситуации эквивалентны пустому файлу (например, для парсинга конфигов)
Или вручную указывать, что чем считать, а вышеперечисленные флаги сделать наборами стандартных установок. А то эти 50 оттенков NULL-ов часто раздражают.
Любишь забивать на race condition'ы?
FileExists после FileOpen покажет только то, что файл существовал (или не существовал) после FileOpen, а не во время него.
А если я открываю файл в режиме аппенда, то я сам же и создам новый. Кстати, "не могу открыть существующий файл" тоже реальный сценарий, скажем, когда работаешь с файлами на сетевых шарах или в c:\program files
То что может не хватить прав на открытие файлов я в курсе. В Линуксе такое запросто случается.
мем "нарастающая гениальность/бредовость" с этими ребятами всё более и более актуален
А все ошибки уходят на экран в зависимости от error_reporting/display_errors в cpp.ini?
Если есть функция, которая примет StringView и вернёт StringView, то мой пример можно немного усложнить так, чтобы код компилировался, но сегфолтил. Либо я не понимаю, какие типы когда приводятся к каким.
> Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
Получается, нормальный способ принимать текст -- это StringView, но файл к нему не кастится, то есть нужен явный каст, а "красота" работает только если писать String x = OS.FileOpen(...)?
К чему кастится RTake<FileReader>? К String или StringView?
Если функция вернёт локальную строчку как StringView, то конечно в результате будет неопределённый мусор. Ну в общем это правило C++ программистам известно - никогда не возвращать ссылки на локальный объект и не хранить ссылки на временные объекты.
Вот такое уже упадёт, потому что x будет StringView, указывающий на временный String.
Да, "красота" работает только с контейнерами.
От RTake<FileReader> может конструироваться любой контейнер символов, в частности - String. StringView - не контейнер, а просто удобная ссылка на массив char.
а шо таке? Не умеете в исключения?
> никаких неожиданных исключений.
А вы ждите. Надейтесь, верьте, встречайте.