- 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++ громоздкий и невыразительный, просто не умеют его готовить.
j123123 03.04.2017 06:54 # +1
Данный код никоим образом не показывает негромоздкость и выразительность C++. И готовить его ты точно не умеешь.
j123123 03.04.2017 06:59 # +4
gammaker 03.04.2017 21:21 # 0
Antervis 03.04.2017 08:39 # 0
ASD_77 03.04.2017 13:11 # −2
А по мне. просто берешь cs2cpp и понвертишь mscorelib в с++ и выкидывешь мусор от тудава. И будет счастье
Antervis 03.04.2017 13:26 # +4
Всей жизни не хватит
inkanus-gray 03.04.2017 13:36 # +1
doktor 03.04.2017 13:41 # −15
inkanus-gray 03.04.2017 13:45 # +2
ASD_77 03.04.2017 14:06 # 0
roman-kashitsyn 03.04.2017 14:24 # 0
Как это связано со стандартной библиотекой? Чем ужасен = 0?
ASD_77 03.04.2017 14:37 # −1
roman-kashitsyn 03.04.2017 14:39 # 0
Как это относится к = 0? Причём здесь стандартизация С++?
ASD_77 03.04.2017 14:44 # 0
ASD_77 03.04.2017 14:46 # 0
roman-kashitsyn 03.04.2017 15:04 # +2
Ничего ужасного в = 0 не вижу, это гораздо лучше ключевого слова pure, которое по отношению к функциям означает совсем другое.
Antervis 03.04.2017 16:02 # 0
1024-- 03.04.2017 17:17 # 0
Antervis 03.04.2017 16:01 # +2
bormand 03.04.2017 22:14 # −12
А потом добавили внезапные =delete и =default...
Ждём =new вместо virtual, =if вместо enable if, =for для генереции метушни, =switch для паттерн-матчинга и =auto чтобы конпелятор сам что-нибудь подходящее выбрал.
Antervis 04.04.2017 04:44 # 0
в языке есть конструкция, где функция чему-то приравнивается в объявлении, есть ключевые слова delete и default, хм, какие же конструкции языка придумает комитет чтобы реализовать запрет использования и реализацию функции по умолчанию?
CHayT 04.04.2017 12:30 # +5
=42 -- вывод кода функции на основании сигнатуры.
=throw -- ничего не делает, но при этом не даёт определить такой метод.
=1997 -- все перегруженные методы будут заанроллены, и запрещает использование в них плавающего питуха.
Antervis 04.04.2017 14:45 # 0
так есть же [[deprecated]]
huesto 09.04.2017 02:47 # −15
gammaker 03.04.2017 21:54 # 0
STL не поддерживает копирование потоков. Приходится передавать их по ссылке. А что если функция споткнётся на ошибке в файле? Тогда позиция в потоке станет неопределённой. Она не может откатить его назад и не во всех случаях знает, где конец нужного куска.
А ещё файлы STL плохо отображаются в отладчике. Никакой полезной информации там нет. А мой FileReader в отладчике студии отображает даже ещё не прочитанное содержимое файла, текущую позицию в нём и его размер, что очень помогает в отладке.
Даже мой аналог std::vector лучше отображается в отладчике студии - можно видеть первые несколько элементов, не раскрывая содержимое объекта.
А ещё стандартная библиотека не предоставляет возможности быстро переводить число в строку и наоборот. Все её возможности даже медленнее sprintf, а моя либа делает это быстрее sprintf в 2 раза и при этом намного удобнее и безопаснее. А контейнеры в строку стандартная библиотека в принципе переводить не умеет. А моя умеет, причём в любом формате и работает даже с контейнерами STL, не подозревая даже об их существовании.
Bobik 03.04.2017 22:14 # 0
Потому что далеко не все потоки копируются?
Что происходит при копировании потока сокета? Ведь с прикладной точки зрения состояние для чтения зависит от того, что мы в него записали. Если код, который что-то читает из сокета, сломался, нормально продолжить с ним работу почти невозможно. Даже в posix почему-то отдельный lseek() для dup()нутых дескрипторов не осилили.
gammaker 03.04.2017 22:43 # 0
Другой InputStream и сокет можно только перемещать.
Antervis 04.04.2017 04:51 # +4
Оборачивай в std::shared_ptr
> А мой FileReader в отладчике студии ...
авторы stl клали большой и толстый на отладчик студии. У них не один компилятор, не одна целевая платформа и не одна ide, о которых надо помнить
gammaker 04.04.2017 12:21 # +2
Авторы реализации STL для студии должны были сделать её удобной для использования именно в студии.
И в принципе законы логики в разных IDE/компиляторах не меняются. Меняется лишь способ настройки отладчика (natvis, python для gdb и т.п.), чтобы он выводил то, что надо. Когда я разберусь с визуализацией в gdb, я и её сделаю для своей либы. И это покроет почти все основные компиляторы и все платформы. Сама по себе библиотека уже компилируется всеми основными компиляторами под винду, линукс и андроид.
gammaker 03.04.2017 22:37 # −1
Bobik 03.04.2017 18:05 # +1
gammaker 03.04.2017 21:19 # 0
Переменная x будет иметь тип RTake<FileReader>, который говорит, что надо взять столько-то символов из файла. И output.PrintLine прочитает эту строчку из файла. Несколько раз читать одно и то же из файла не очень хорошо в плане производительности, поэтому вместо TakeByLine лучше использовать просто ByLine. Тогда в x будет String.
Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
И да, функции не должны принимать имя файла, если только они зачем-то не хотят его запомнить. Они дожны принимать InputStream и работать с любыми потоками, а не только с файлами.
roman-kashitsyn 03.04.2017 22:03 # +2
Т.е. вот этот код интерпретирует файлы, которые не получилось открыть, как пустые файлы? Удобно: зачем париться по поводу ошибок, если можно их игнорировать. Главное, чтобы код красивый был.
gammaker 03.04.2017 22:53 # 0
Если произошла ошибка и объект status не был проверен, то деструктор FatalErrorStatus крашнет программу с сообщением о том, что файл "TestFileSyncIO.txt" не удалось открыть по такой-то причине. Если ошибку нужно молча залогировать, но не крашиться, вместо FatalErrorStatus надо создать другой ErrorStatus с ассоциированным логом.
То есть случайно ошибку заигнорить не получится, потому что компилятор потребует передать аргумент. Но при желании ошибку легко явно заигнорить передачей специального игнорящего объекта ошибки, когда пустые файлы - то что надо. Если игнорить нельзя, можно завести FatalErrorStatus. Код страшнее от этого особо не становится и все ошибки обрабатываются явно - никаких неожиданных исключений.
gost 04.04.2017 13:40 # 0
gammaker 04.04.2017 14:25 # 0
Компилятор не позволит ошибкам распространяться неконтролируемо. Ошибку нужно передавать явно. При желании некритичную ошибку легко заигнорить и получить логичное поведение по умолчанию. Не нужно писать громоздкие try-catch и каждый раз реализовывать это поведение самому. А если забыть try-catch в некритичном месте, можно получить креш вместо по прежнему работающей программы.
Antervis 04.04.2017 14:46 # 0
gammaker 04.04.2017 15:17 # 0
Antervis 04.04.2017 19:33 # 0
1. позитивный и негативный сценарий обработки полностью обособленны. Скажем, пять вызовов, каждый из которых потенциально вызывает ошибку, можно обработать в одном месте
2. исключения строго типизированы и ловятся по типу. Это позволяет, например, на ошибку ввода реагировать всплывающим окошком, а фатальную ошибку ловить аж в main.
3. не пойманное исключение невозможно не заметить. А текст ошибки будет виден даже в релиз-билде.
4. Бывают сценарии, где ошибки крайне маловероятны. Скажем, система вряд ли запретит вам открыть только что этим же приложением созданный/скачанный файл.
А если переживаете за неявность выброшенного исключения - помечайте функции noexcept(false). Радикально, конечно, но двусмысленность исключена
roman-kashitsyn 04.04.2017 21:21 # +1
Это сказка, которую постоянно пытаются продать. На самом деле если делать нормальную обработку ошибок, то чаще всего как раз понять, какая именно строчка кинула ошибку, и тут начинаются такие костыли/кубометры try/catch, что уж лучше бы явные объекты ошибок передавать.
Antervis 05.04.2017 06:32 # 0
Во-первых, если у меня пять запросов к устройству, мне необязательно явно указывать, какой именно из них вылетел из-за отсутствия соединения (потому что 99.9999% что первый, а даже если и не он, то какая разница?). Во-вторых, скажем, вряд ли ошибка "не могу скопировать файл" вылетит в строчке, которая его открывает. По факту, если у вас разные вызовы кидают одинаковые исключения, которые надо по разному обрабатывать - это проблема архитектуры приложения, а не технологии.
roman-kashitsyn 05.04.2017 12:10 # 0
Надеюсь, это зелёным. Я ведь хочу не постфактум из лога узнать, почему произошла ошибка, а в приложении её обработать.
> какой именно из них вылетел из-за отсутствия соединения
Это зависит от стратегии обработки ошибок. Иногда вывести "устройство не работает :(" и доблестно умереть недостаточно. Я довольно долго работал над сервисом, где в случае разрыва соединения лучше всего не падать, а подождать восстановления соединения и продолжить выполнение с того места, где всё ещё было нормально.
В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.
Antervis 05.04.2017 12:25 # 0
Так ты ловишь какое-нибудь NonCriticalException исключение по типу, а потом обрабатываешь. А если поймал FatalException, то доблестно умираешь.
> В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.
Зависит от того, как используется асинхронность. asio с его callback'ами - там не столько сложно (всё равно есть "линейные" участки), сколько не нужно. А ежели асинхронность на фьючерсах/промисах, то ничто не мешает использовать исключения. std::error_code и std::system_error покрывают очень большой набор сценариев обработки ошибок
roman-kashitsyn 05.04.2017 12:31 # 0
Antervis 06.04.2017 06:48 # 0
gammaker 04.04.2017 22:16 # 0
2) Как-то мне не приходилось сталкиваться с несколькими разными ошибками сразу. Для обработки достаточно информации о том, успешна операция или нет, а для изучения причины есть подробная информация в логе. И ошибки обрабатываю до того, как станет несколько ошибок, которые нужно различать.
3) У меня в конце п. 1 написано про то же самое. Только плюс к этому про ошибку в принципе нельзя забыть. Если функция может сфейлиться, она будет ожидать ссылку на объект ошибки, а не молча кидать исключения.
4) А причём здесь достоинства исключений?
Даже если я помечу noexcept(false), я могу забыть обратить на него внимание, потому что компилятор не будет меня принуждать писать обработчик или что-то куда-то передавать.
AntiSpam 04.04.2017 22:33 # −10
Выставил тебе заслуженный минус.
gammaker 04.04.2017 22:39 # 0
Получается естественная раскрутка стека без сюрпризов. И можно не беспокоиться за утечки не-RAII ресурсов, которые могут возникнуть при неожиданном вылете исключения.
У меня в либе подобная, только чуть более упрощённая, схема уже больше года работает и я ни разу не пожалел. Всё мега-удобно.
Antervis 05.04.2017 06:36 # 0
gammaker 05.04.2017 10:56 # 0
Это как с const, RAII и другими фишккми, которые помогают не допустить ошибку: компилятор не даст случайно изменить константный объект, и не надо самому освобождать ресурсы, потому что об этом позаботится деструктор.
roman-kashitsyn 06.04.2017 12:04 # 0
Не совсем понятно, что должно происходить, когда один и тот же status просовывается в несколько вызовов, вот в коде вроде такого:
Какой из двух статусов должен выжить, если ошибка вдруг происходит в двух местах (прогу запустили в корне FS, например)?
gammaker 06.04.2017 14:31 # 0
Antervis 06.04.2017 14:38 # 0
gammaker 06.04.2017 16:01 # 0
roman-kashitsyn 06.04.2017 16:18 # +2
gammaker 06.04.2017 16:47 # +1
Antervis 06.04.2017 17:36 # +1
1024-- 06.04.2017 17:58 # 0
ФЛАГ_БАНК - "не могу открыть файл", "файла не существует" и "открылся существующий файл, но он пустой" - разные ситуации
ФЛАГ_СТД - "файла не существует" и "открылся существующий файл, но он пустой" эквивалентны
ФЛАГ_СКРИПТ - все эти ситуации эквивалентны пустому файлу (например, для парсинга конфигов)
Или вручную указывать, что чем считать, а вышеперечисленные флаги сделать наборами стандартных установок. А то эти 50 оттенков NULL-ов часто раздражают.
gammaker 06.04.2017 18:12 # 0
bormand 06.04.2017 18:13 # −15
Любишь забивать на race condition'ы?
FileExists после FileOpen покажет только то, что файл существовал (или не существовал) после FileOpen, а не во время него.
gammaker 06.04.2017 19:17 # +1
Antervis 06.04.2017 19:42 # +1
А если я открываю файл в режиме аппенда, то я сам же и создам новый. Кстати, "не могу открыть существующий файл" тоже реальный сценарий, скажем, когда работаешь с файлами на сетевых шарах или в c:\program files
gammaker 06.04.2017 19:51 # 0
То что может не хватить прав на открытие файлов я в курсе. В Линуксе такое запросто случается.
roman-kashitsyn 06.04.2017 18:47 # 0
gammaker 06.04.2017 19:18 # 0
Antervis 06.04.2017 19:50 # 0
мем "нарастающая гениальность/бредовость" с этими ребятами всё более и более актуален
Bobik 03.04.2017 22:19 # +3
А все ошибки уходят на экран в зависимости от error_reporting/display_errors в cpp.ini?
Если есть функция, которая примет StringView и вернёт StringView, то мой пример можно немного усложнить так, чтобы код компилировался, но сегфолтил. Либо я не понимаю, какие типы когда приводятся к каким.
> Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
Получается, нормальный способ принимать текст -- это StringView, но файл к нему не кастится, то есть нужен явный каст, а "красота" работает только если писать String x = OS.FileOpen(...)?
К чему кастится RTake<FileReader>? К String или StringView?
gammaker 03.04.2017 23:09 # 0
Если функция вернёт локальную строчку как StringView, то конечно в результате будет неопределённый мусор. Ну в общем это правило C++ программистам известно - никогда не возвращать ссылки на локальный объект и не хранить ссылки на временные объекты.
Вот такое уже упадёт, потому что x будет StringView, указывающий на временный String.
Да, "красота" работает только с контейнерами.
От RTake<FileReader> может конструироваться любой контейнер символов, в частности - String. StringView - не контейнер, а просто удобная ссылка на массив char.
Antervis 04.04.2017 04:56 # 0
а шо таке? Не умеете в исключения?
> никаких неожиданных исключений.
А вы ждите. Надейтесь, верьте, встречайте.
gammaker 04.04.2017 12:26 # 0
Antervis 04.04.2017 14:47 # 0
gammaker 04.04.2017 16:06 # 0