1. C++ / Говнокод #22727

    −19

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    44. 44
    45. 45
    46. 46
    47. 47
    48. 48
    49. 49
    50. 50
    51. 51
    52. 52
    53. 53
    54. 54
    55. 55
    56. 56
    57. 57
    58. 58
    59. 59
    60. 60
    61. 61
    62. 62
    63. 63
    64. 64
    65. 65
    66. 66
    67. 67
    68. 68
    69. 69
    70. 70
    71. 71
    72. 72
    73. 73
    74. 74
    75. 75
    76. 76
    77. 77
    78. 78
    79. 79
    80. 80
    81. 81
    82. 82
    83. 83
    84. 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++ громоздкий и невыразительный, просто не умеют его готовить.

    Запостил: gammaker, 02 Апреля 2017

    Комментарии (72) RSS

    • >Те, кому кажется, что C++ громоздкий и невыразительный, просто не умеют его готовить.

      Данный код никоим образом не показывает негромоздкость и выразительность C++. И готовить его ты точно не умеешь.
      Ответить
      • Вообще, советую почитать исходники BOOST, только осторожно, заранее приготовьте ведро или тазик чтоб блевать
        Ответить
        • Видел уже. Просто они не умеют нормально готовить C++.
          Ответить
    • с++/stl хоронят так часто, что начинает казаться, что его придумали Грейджои
      Ответить
    • вижу тут идет подобие C# mscorlib - либы. stl больше чем достаточно что бы сделать все что надо. в boost-е не понимаю какого хрена она кмпилиться на 9GB :)

      А по мне. просто берешь cs2cpp и понвертишь mscorelib в с++ и выкидывешь мусор от тудава. И будет счастье
      Ответить
      • > А по мне. просто берешь cs2cpp и понвертишь mscorelib в с++ и выкидывешь мусор от тудава

        Всей жизни не хватит
        Ответить
      • А вдруг после выкидывания мусора ничего не останется?
        Ответить
        • показать все, что скрытоРодители запрещали детям "выносить сор из избы", поэтому, из-за антисанитарных условий, семья часто переезжала.
          Ответить
          • Выходит, что новые языки программирования изобретали только ради того, чтобы не выносить мусор из стандартной библиотеки?
            Ответить
            • именно. Чего только стоит "= 0" в обьявлении метода в с++ :)
              Ответить
              • > именно. Чего только стоит "= 0" в обьявлении метода в с++ :)

                Как это связано со стандартной библиотекой? Чем ужасен = 0?
                Ответить
                • тем что были времена когда стандарты в c++ изменить было не возможно поэтому выкруливались как могли и лепили "горбатого до стенки" :)
                  Ответить
                  • > стандарты в c++ изменить было не возможно

                    Как это относится к = 0? Причём здесь стандартизация С++?
                    Ответить
                    • he reason =0 is used is that Bjarne Stroustrup didn't think he could get another keyword, such as "pure" past the C++ community at the time the feature was being implemented. This is described in his book, The Design & Evolution of C++, section 13.2.3:
                      Ответить
                      • иненно это и обьясняет появление c# java и т.д. что бы не выносить мусор из C++
                        Ответить
                      • Так проблема не в том, что стандарты тяжело изменить, а в том, что добавлять новые ключевые слова в стандарт языка, на котором уже написана куча кода, никто не хочет без особой на то необходимости.

                        Ничего ужасного в = 0 не вижу, это гораздо лучше ключевого слова pure, которое по отношению к функциям означает совсем другое.
                        Ответить
                        • могло бы быть pure_virtual
                          Ответить
                        • А почему нельзя было ввести ключевое слово pure? Оно вводит неразрешаемую неоднозначность грамматики? Или идеологические причины?

                          pure int f(); // = 0
                          pure f(); // тип
                          int pure(); // имя
                          pure pure(pure);
                          Ответить
                      • кстати, даже хорошо что так вышло. Конструкция = 0 намного нагляднее, т.к. сильнее выделяется на фоне строк типа virtual const char *func() const noexcept, чем какое-нибудь pure.
                        Ответить
                        • показать все, что скрыто> конструкция = 0 намного нагляднее
                          А потом добавили внезапные =delete и =default...

                          Ждём =new вместо virtual, =if вместо enable if, =for для генереции метушни, =switch для паттерн-матчинга и =auto чтобы конпелятор сам что-нибудь подходящее выбрал.
                          Ответить
                          • > А потом добавили внезапные =delete и =default...
                            в языке есть конструкция, где функция чему-то приравнивается в объявлении, есть ключевые слова delete и default, хм, какие же конструкции языка придумает комитет чтобы реализовать запрет использования и реализацию функции по умолчанию?
                            Ответить
                          • =shit -- объявляем метод deprecated.
                            =42 -- вывод кода функции на основании сигнатуры.
                            =throw -- ничего не делает, но при этом не даёт определить такой метод.
                            =1997 -- все перегруженные методы будут заанроллены, и запрещает использование в них плавающего питуха.
                            Ответить
      • mscorlib не предназначена для C++. Она основана на сборщике мусора и не поддерживает киллер-фичу плюсов - RAII. Но много лет назад когда я увидел шарп я понял, что с C++ что-то не так - он позволяет сделать нормальный интерфейс и удобство шарпа, но в стандартной библиотеке его нет.
        STL не поддерживает копирование потоков. Приходится передавать их по ссылке. А что если функция споткнётся на ошибке в файле? Тогда позиция в потоке станет неопределённой. Она не может откатить его назад и не во всех случаях знает, где конец нужного куска.
        А ещё файлы STL плохо отображаются в отладчике. Никакой полезной информации там нет. А мой FileReader в отладчике студии отображает даже ещё не прочитанное содержимое файла, текущую позицию в нём и его размер, что очень помогает в отладке.
        Даже мой аналог std::vector лучше отображается в отладчике студии - можно видеть первые несколько элементов, не раскрывая содержимое объекта.
        А ещё стандартная библиотека не предоставляет возможности быстро переводить число в строку и наоборот. Все её возможности даже медленнее sprintf, а моя либа делает это быстрее sprintf в 2 раза и при этом намного удобнее и безопаснее. А контейнеры в строку стандартная библиотека в принципе переводить не умеет. А моя умеет, причём в любом формате и работает даже с контейнерами STL, не подозревая даже об их существовании.
        Ответить
        • > STL не поддерживает копирование потоков. Приходится передавать их по ссылке. А что если функция споткнётся на ошибке в файле? Тогда позиция в потоке станет неопределённой. Она не может откатить его назад и не во всех случаях знает, где конец нужного куска.

          Потому что далеко не все потоки копируются?
          Что происходит при копировании потока сокета? Ведь с прикладной точки зрения состояние для чтения зависит от того, что мы в него записали. Если код, который что-то читает из сокета, сломался, нормально продолжить с ним работу почти невозможно. Даже в posix почему-то отдельный lseek() для dup()нутых дескрипторов не осилили.
          Ответить
          • У меня есть два типа обобщённых потоков: InputStream и ForwardStream. InputStream самый общий и копирование не поддерживает. ForwardStream копирование поддерживает. Если функция принимает InputStream по значению, то файл или любой ForwardStream туда можно передать двумя способами - перемещением или копированием.
            Другой InputStream и сокет можно только перемещать.
            Ответить
        • > STL не поддерживает копирование потоков.

          Оборачивай в std::shared_ptr

          > А мой FileReader в отладчике студии ...

          авторы stl клали большой и толстый на отладчик студии. У них не один компилятор, не одна целевая платформа и не одна ide, о которых надо помнить
          Ответить
          • И очень быстро это приведёт к циклическим ссылкам. В C++ нужен другой подход, где чётко определено, кто чем владеет.

            Авторы реализации STL для студии должны были сделать её удобной для использования именно в студии.
            И в принципе законы логики в разных IDE/компиляторах не меняются. Меняется лишь способ настройки отладчика (natvis, python для gdb и т.п.), чтобы он выводил то, что надо. Когда я разберусь с визуализацией в gdb, я и её сделаю для своей либы. И это покроет почти все основные компиляторы и все платформы. Сама по себе библиотека уже компилируется всеми основными компиляторами под винду, линукс и андроид.
            Ответить
      • <удалил дубликат>
        Ответить
    • Прям библиотека граблей и выстрелов в ногу. Если файл не закрылся, деструктор кинет исключение? Если кто-то напишет
      auto x = TakeByLine(OS.FileOpen("TestFileSyncIO.txt")).First(); output.PrintLine(x);
      , то получит сегфолт? А неявное преобразование файла в строку хорошо бьет по голове, когда уже открытый файл передаётся в функцию, которая принимает только имя файла.
      Ответить
      • В моей либе исключения не используются. Не очень понятно, почему файл может не закрыться, но если внезапно он не закрылся, значит такова судьба и ничего с этим не поделать всё равно. При завершении процесса система всё равно должна освободить все относящиеся к нему ресурсы. А продолжать выполнение приложения это не мешает.
        Переменная x будет иметь тип RTake<FileReader>, который говорит, что надо взять столько-то символов из файла. И output.PrintLine прочитает эту строчку из файла. Несколько раз читать одно и то же из файла не очень хорошо в плане производительности, поэтому вместо TakeByLine лучше использовать просто ByLine. Тогда в x будет String.
        Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
        И да, функции не должны принимать имя файла, если только они зачем-то не хотят его запомнить. Они дожны принимать InputStream и работать с любыми потоками, а не только с файлами.
        Ответить
        • > В моей либе исключения не используются.

          Т.е. вот этот код
          for(auto str: OS.FileOpen("TestFileSyncIO.txt").ByLine(buf))
          		sumLength += str.Length();
          интерпретирует файлы, которые не получилось открыть, как пустые файлы? Удобно: зачем париться по поводу ошибок, если можно их игнорировать. Главное, чтобы код красивый был.
          Ответить
          • Я собираюсь внедрять в свою либу новую систему обработки ошибок.
            FatalErrorStatus status;
            for(auto str: OS.FileOpen("TestFileSyncIO.txt", status).ByLine(buf))
            		sumLength += str.Length();

            Если произошла ошибка и объект status не был проверен, то деструктор FatalErrorStatus крашнет программу с сообщением о том, что файл "TestFileSyncIO.txt" не удалось открыть по такой-то причине. Если ошибку нужно молча залогировать, но не крашиться, вместо FatalErrorStatus надо создать другой ErrorStatus с ассоциированным логом.
            То есть случайно ошибку заигнорить не получится, потому что компилятор потребует передать аргумент. Но при желании ошибку легко явно заигнорить передачей специального игнорящего объекта ошибки, когда пустые файлы - то что надо. Если игнорить нельзя, можно завести FatalErrorStatus. Код страшнее от этого особо не становится и все ошибки обрабатываются явно - никаких неожиданных исключений.
            Ответить
            • Молодец, до изобретения исключений тебе осталось совсем немного.
              Ответить
              • Нет, это хоть и похоже на исключения, но лучше. Стек раскручивается естественным образом, поэтому не будет утечек, если что-то забыли обернуть в RAII-обёртку.
                Компилятор не позволит ошибкам распространяться неконтролируемо. Ошибку нужно передавать явно. При желании некритичную ошибку легко заигнорить и получить логичное поведение по умолчанию. Не нужно писать громоздкие try-catch и каждый раз реализовывать это поведение самому. А если забыть try-catch в некритичном месте, можно получить креш вместо по прежнему работающей программы.
                Ответить
                • а ты не забывай
                  Ответить
                  • Ну вот в моём случае компилятор не даст забыть. И по прототипу функции видно что в этой функции в принципе может произойти ошибка. Отсутствие же noexcept может означать что его просто забыли поставить или не поставили, потому что легаси код, написанный, когда его ещё не было.
                    Ответить
                    • у исключений есть несколько замечательных достоинств:
                      1. позитивный и негативный сценарий обработки полностью обособленны. Скажем, пять вызовов, каждый из которых потенциально вызывает ошибку, можно обработать в одном месте
                      2. исключения строго типизированы и ловятся по типу. Это позволяет, например, на ошибку ввода реагировать всплывающим окошком, а фатальную ошибку ловить аж в main.
                      3. не пойманное исключение невозможно не заметить. А текст ошибки будет виден даже в релиз-билде.
                      4. Бывают сценарии, где ошибки крайне маловероятны. Скажем, система вряд ли запретит вам открыть только что этим же приложением созданный/скачанный файл.

                      А если переживаете за неявность выброшенного исключения - помечайте функции noexcept(false). Радикально, конечно, но двусмысленность исключена
                      Ответить
                      • > позитивный и негативный сценарий обработки полностью обособленны. Скажем, пять вызовов, каждый из которых потенциально вызывает ошибку

                        Это сказка, которую постоянно пытаются продать. На самом деле если делать нормальную обработку ошибок, то чаще всего как раз понять, какая именно строчка кинула ошибку, и тут начинаются такие костыли/кубометры try/catch, что уж лучше бы явные объекты ошибок передавать.
                        Ответить
                        • #define MyException(x) private::_myException(x,__FILE__,__LINE_ _)

                          Во-первых, если у меня пять запросов к устройству, мне необязательно явно указывать, какой именно из них вылетел из-за отсутствия соединения (потому что 99.9999% что первый, а даже если и не он, то какая разница?). Во-вторых, скажем, вряд ли ошибка "не могу скопировать файл" вылетит в строчке, которая его открывает. По факту, если у вас разные вызовы кидают одинаковые исключения, которые надо по разному обрабатывать - это проблема архитектуры приложения, а не технологии.
                          Ответить
                          • > #define MyException(x) private::_myException(x,__FILE__,__LINE_ _)

                            Надеюсь, это зелёным. Я ведь хочу не постфактум из лога узнать, почему произошла ошибка, а в приложении её обработать.

                            > какой именно из них вылетел из-за отсутствия соединения

                            Это зависит от стратегии обработки ошибок. Иногда вывести "устройство не работает :(" и доблестно умереть недостаточно. Я довольно долго работал над сервисом, где в случае разрыва соединения лучше всего не падать, а подождать восстановления соединения и продолжить выполнение с того места, где всё ещё было нормально.

                            В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.
                            Ответить
                            • > Надеюсь, это зелёным. Я ведь хочу не постфактум из лога узнать, почему произошла ошибка, а в приложении её обработать.

                              Так ты ловишь какое-нибудь NonCriticalException исключение по типу, а потом обрабатываешь. А если поймал FatalException, то доблестно умираешь.

                              > В прочем, код был асинхронный, в асинхронном коде про исключения вообще можно забыть.

                              Зависит от того, как используется асинхронность. asio с его callback'ами - там не столько сложно (всё равно есть "линейные" участки), сколько не нужно. А ежели асинхронность на фьючерсах/промисах, то ничто не мешает использовать исключения. std::error_code и std::system_error покрывают очень большой набор сценариев обработки ошибок
                              Ответить
                              • Своя, более другая асинхронность. Можно считать, что это запросы к базе данных, которым даёшь колбэк, они его вызывают с результатом и кодом ошибки. Пачки N+1 запросов особенно весело так реализовывать.
                                Ответить
                                • Хороший подход. Пока не попадается какой-нибудь противный протокол, в котором не установишь однозначного соответствия request/response. И приходится эти асинхронные вызовы синхронизировать...
                                  Ответить
                      • 1) У меня также, учитывая, что сфейлившаяся функция записывает информацию об ошибке и возвращает пустой объект, который ничего не делает и не приводит к крешу. Выполнение продолжается до конца и дальше уже ставится специальный if - обработчик ошибки, который проверяет, сфейлилась ли операция. И если да, обрабатывает ошибку. Если забыть поставить этот if с проверкой, ошибка крашнет программу, мол забыли обработать ошибку.
                        2) Как-то мне не приходилось сталкиваться с несколькими разными ошибками сразу. Для обработки достаточно информации о том, успешна операция или нет, а для изучения причины есть подробная информация в логе. И ошибки обрабатываю до того, как станет несколько ошибок, которые нужно различать.
                        3) У меня в конце п. 1 написано про то же самое. Только плюс к этому про ошибку в принципе нельзя забыть. Если функция может сфейлиться, она будет ожидать ссылку на объект ошибки, а не молча кидать исключения.
                        4) А причём здесь достоинства исключений?

                        Даже если я помечу noexcept(false), я могу забыть обратить на него внимание, потому что компилятор не будет меня принуждать писать обработчик или что-то куда-то передавать.
                        Ответить
                        • показать все, что скрыто->У меня также, учитывая, что сфейлившаяся функция записывает информацию об ошибке и возвращает пустой объект, который ничего не делает и не приводит к крешу

                          Выставил тебе заслуженный минус.
                          Ответить
                          • Само по себе может это и плохо, но в сочетании с объектом ошибки, который не даст её игнорировать, это даёт хорошую альтернативу исключениям. Обработка тоже в одном месте и в то же время никакого мусора с проверками кодов ошибок.
                            Получается естественная раскрутка стека без сюрпризов. И можно не беспокоиться за утечки не-RAII ресурсов, которые могут возникнуть при неожиданном вылете исключения.
                            У меня в либе подобная, только чуть более упрощённая, схема уже больше года работает и я ни разу не пожалел. Всё мега-удобно.
                            Ответить
                        • что ж вы за программист такой. Обо всём на свете забываете, noexcept(false) не заметите, и вас не волнует, что ваш код продолжит выполнение после ошибки.
                          Ответить
                          • Я забываю не часто, но всякое бывает. И лучше, если компилятор в случае чего на это укажет, чем это придётся лишний раз держать в голове.
                            Это как с const, RAII и другими фишккми, которые помогают не допустить ошибку: компилятор не даст случайно изменить константный объект, и не надо самому освобождать ресурсы, потому что об этом позаботится деструктор.
                            Ответить
            • > FatalErrorStatus status;

              Не совсем понятно, что должно происходить, когда один и тот же status просовывается в несколько вызовов, вот в коде вроде такого:
              FatalErrorStatus status;
              OS.FileOpen("intput.txt", status)
                .ByLine(buf)
                .Transform(Frobnicate)
                .WriteToAndClose(OS.FileOpenOverwrite("frobnicated.txt", status));
              Какой из двух статусов должен выжить, если ошибка вдруг происходит в двух местах (прогу запустили в корне FS, например)?
              Ответить
              • Ошибка может быть только одна. Если status установлен, то последующие вызовы не будут ничего делать. FileOpenOwerwrite не будет открывать файл для записи, а записывать будет некуда и нечего
                Ответить
                • Погоди. По идее, первый вызов должен установить status в какой-нибудь FileNotFound, а второй - в FileIsNotOpen. В конце концов, попытка операции над некорректным файловым объектом должна выдавать ошибку, разве нет?
                  Ответить
                  • У меня все объекты корректные. Неоткрытый файл - пустой файл. Проверка конца файла сработает сразу и ни одной строчки считано не будет - будет пустой диапазон строк файла, который не приведёт ни к каким записям в файл.
                    Ответить
                    • В общем, у тебя в зависимости от того, в каком порядке компилятор решит вычислять временные объекты, будет либо ошибка открытия на чтения либо ошибка открытия на запись.
                      Ответить
                      • Хм, что-то я не подумал, что тут порядок вызова функций неопределён. Тогда здесь может создасться пустой файл перед тем, как попытаться открыть файл для чтения.
                        Ответить
                        • а ты не подумал о том, что "не могу открыть файл", "файла не существует" и "открылся существующий файл, но он пустой" - это немного разные сценарии, и их как правило надо по разному обрабатывать?
                          Ответить
                          • Кстати, я бы предлагал впилить флаг важности таких различий.
                            ФЛАГ_БАНК - "не могу открыть файл", "файла не существует" и "открылся существующий файл, но он пустой" - разные ситуации
                            ФЛАГ_СТД - "файла не существует" и "открылся существующий файл, но он пустой" эквивалентны
                            ФЛАГ_СКРИПТ - все эти ситуации эквивалентны пустому файлу (например, для парсинга конфигов)

                            Или вручную указывать, что чем считать, а вышеперечисленные флаги сделать наборами стандартных установок. А то эти 50 оттенков NULL-ов часто раздражают.
                            Ответить
                          • Подумал. Их вполне можно отличать, если нужно. Если открыт реально пустой файл - это вовсе не ошибка. Если файл не удалось открыть, то при обработке ошибки просто проверяем, существовал ли он в принципе вызовом OS.FileExists и на основании этого вызова определяем, первый это случай или второй.
                            Ответить
                            • показать все, что скрыто> Если файл не удалось открыть, то при обработке ошибки просто проверяем, существовал ли он в принципе вызовом OS.FileExists
                              Любишь забивать на race condition'ы?

                              FileExists после FileOpen покажет только то, что файл существовал (или не существовал) после FileOpen, а не во время него.
                              Ответить
                              • Ну ок, значит придётся встроить в объект ошибки возможность задавать код ошибки, который можно проверять в тех случаях, когда нужно их отличать.
                                Ответить
                            • > Если файл не удалось открыть, то при обработке ошибки просто проверяем, существовал ли он в принципе

                              А если я открываю файл в режиме аппенда, то я сам же и создам новый. Кстати, "не могу открыть существующий файл" тоже реальный сценарий, скажем, когда работаешь с файлами на сетевых шарах или в c:\program files
                              Ответить
                              • Ошибка несуществования файла бывает только при открытии для чтения. OS.FileOpenOverwrite и OS.FileOpenAppend в случае отсутствия файла создадут новый, если смогут. И только если не смогут или нет доступа к существующему файлу, то выдадут ошибку.
                                То что может не хватить прав на открытие файлов я в курсе. В Линуксе такое запросто случается.
                                Ответить
                        • Попробуй рассматривать ошибки как контейнеры, которые содержат либо что-то полезное, либо ошибку.
                          Ответить
                          • Боюсь, громоздковато получится. Но если вдруг найду в своём методе фатальные недостатки, то рассмотрю этот вариант.
                            Ответить
                          • Берем std::any, проверяем наличие в нем ошибки отловом std::bad_any_cast в try/catch, а потом, соответственно, против возможных результатов. Так, в принципе, можно возвращать из функции объекты результатов/ошибок любого типа, делая функциональность даже круче чем с exception'ами, которые поддерживают всего один тип возвращаемого значения

                            мем "нарастающая гениальность/бредовость" с этими ребятами всё более и более актуален
                            Ответить
        • >В моей либе исключения не используются
          А все ошибки уходят на экран в зависимости от error_reporting/display_errors в cpp.ini?

          Если есть функция, которая примет StringView и вернёт StringView, то мой пример можно немного усложнить так, чтобы код компилировался, но сегфолтил. Либо я не понимаю, какие типы когда приводятся к каким.

          > Если функция принимает имя файла, то она принимает StringView. А к StringView файл не кастится.
          Получается, нормальный способ принимать текст -- это StringView, но файл к нему не кастится, то есть нужен явный каст, а "красота" работает только если писать String x = OS.FileOpen(...)?

          К чему кастится RTake<FileReader>? К String или StringView?
          Ответить
          • Ошибки не выводятся. Если что-то пошло не так, запускаю дебаг. Но скоро я впилю систему обработки ошибок, которую я описал комментом выше и будет логироваться как надо. Для каждой функции лог выбирать можно будет индивидуально. Специальных файлов конфигурации для либы я навязывать не собираюсь.

            Если функция вернёт локальную строчку как StringView, то конечно в результате будет неопределённый мусор. Ну в общем это правило C++ программистам известно - никогда не возвращать ссылки на локальный объект и не хранить ссылки на временные объекты.
            Вот такое уже упадёт, потому что x будет StringView, указывающий на временный String.
            auto x = TakeByLine(String(OS.FileOpen("TestFileSyncIO.txt"))).First(); output.PrintLine(x);

            Да, "красота" работает только с контейнерами.

            От RTake<FileReader> может конструироваться любой контейнер символов, в частности - String. StringView - не контейнер, а просто удобная ссылка на массив char.
            Ответить
        • > В моей либе исключения не используются

          а шо таке? Не умеете в исключения?

          > никаких неожиданных исключений.

          А вы ждите. Надейтесь, верьте, встречайте.
          Ответить
          • Умею, просто они не нужны. ИМХО, мой новый способ, который я тут в комментах уже описывал, превосходит исключения по всем параметрам и не имеет их недостатков.
            Ответить
            • ваш способ не расширяем.
              Ответить
              • Ну объект ошибки можно по-всякому набивать функционалом, наследоваться от него и т.п.. Я думаю, тут есть большой простор для расширяемости, но в большинстве случаев для принятия решения достаточно знания того, удалась ли операция и выплюнуть в лог/на экран текстовое описание ошибки.
                Ответить

    Добавить комментарий