Comments 82
Так вот: это совсем неправда. C++ — не был задуман «кроссплатформенным языком». Огромное количество способов сделать программу непереносимой. Начиная с того, что никому незвестно априори — влезет ли
-1
в char
или нет.Однако он предназначен для написания кроссплатформенного кода. Но… только и исключительно в случае когда программист использует его «правильно». В этом — принципиальное отличие от Java или, скажем, Go.
… необходимо было дать ответ на несколько ключевых вопросов:
* кто будет пользоваться языком?
* на каких системах будут работать пользователи?
…
Чтобы ответить на второй вопрос, я просто огляделся по сторонам — на каких системах реально использовался C with Classes? Практически на любых: таких маленьких, что на них и компилятор-то не мог работать, до мейнфреймов и супер-компьютеров. Среди ОС были и такие, о которых я даже не слыхивал. Отсюда следовали выводы, что высокая степень переносимости и возможность выполнять кросс-компиляцию абсолютно необходимы…
strlen(«Хабр») в программе, собранной на MSVS выдаст 4; в GCC — 8.
насколько я знаю ни один из них не то что не выдаст, но даже не скомпилирует эти ваши ёлочки
Никогда не мог понять, почему так мелочно некоторые копошатся, даже не пытаясь оценить статью в целом.
Ещё и компилятор зануда, да.
Была б возможность — сам починил бы все опечатки, пропущенные запятые и прочие косяки, которые периодически обнаруживаются мной в статьях.
Статью оценил, но вот такая мелочь мозолит мои дотошные глаза. Профессиональное искажение и все такое. Слишком привык искать ошибки. Темная сторона силы она такая. Я твой отец, Люк.
Так что да — иногда форма кавычек важна.
https://youtu.be/jqxxZIkzi80
Уверен, что работа проведена масштабная, хочется почитать остальные статьи, но… Зачем вы переизобрели Qt? В крайнем случае могли бы его форкнуть и развивать, но вот так… Честно сказать я офигел))
PS: для кроссплатформенной работы с сетью есть обычные сокеты. Curl немного облегчает задачу, но вы ведь все делаете по хардкору)
Ах да, еще pimpl вместо ifdef хорошо бы использовать
1. через ifdef, например:
#ifdef LINUX
# include <my_class_linux_impl.h>
#elif defined WIN32
# include <my_class_win32_impl.h>
...
2. В сборочном скрипте, например, в cmake как-то так:
if (LINUX_OS)
SET(IMPL_FILES my_class_lunux_impl.cpp)
elseif (WIN32_OS)
SET(IMPL_FILES my_class_win32_impl.cpp)
...
endif ()
add_library(TestLibrary ${IMPL_FILES} another_file1.cpp another_file2.cpp)
Не понятно, как вам поможет идиома pimpl для выбора в compile time необходимой реализации. Все равно вам придется выбрать на этапе сборки одну из реализаций, подходящую под целевую систему, одним из вышеперечисленных способов.
Более того, не вижу противоречий в тексте статьи, ведь никто не предлагает писать вот такой вот плохокод:
void some_function()
{
#ifdef WIN32
CallWinAPIMethod();
#elif defined POSIX
CallPOSIXMethod()
#endif
CallAnotherMethod();
#ifdef WIN32
CallAnotherWinAPIMethod();
#elif defined POSIX
CallAnotherPOSIXMethod()
#endif
}
Мы знаем и активно используем паттерны проектирования (в том числе и pimpl). ifdef'ы используем только для выбора подходящей имплементации.
Вы забываете про фатальный недостаток (NIH).
Уже с 4 версии он совсем не GUI-библиотека. Это полноценный кроссплатформенный фреймворк.
По поводу объемности — снова незнание структуры Qt. Как раз ваши классы обертки можно полностью заменить QtCore. Получив из коробки платформонезависимый код (работающий так же на Android и iOS) для файловой системы, unicode-строк, многопоточности и много еще что. Причем оверхед там минимальный. Я бы еще понял что вы C++14 используете там наконец много такое появилось (потоки и файлы).
В виде бонуса с Qt — вы получаете сигналы/слоты и классы для организации собственных структур данных (я про QSharedData и т.п.).
Реальная минимизация зависимостей возможна при отказе от кроссплатформенности (писать на MFC/WinAPI например). Иначе сразу возникает дублирование уже имеющегося — что по настоящему очень мало когда действительно требуется.
Основная причина: завязываясь на Qt, вы теряете кросс-платформенность за рамками списка поддерживаемых систем. И очень сложно потом переделать на что-то другое. Мне однажды довелось выпиливать Qt из бизнес-логики проекта, написанного на Qt, больше не хочу. Это понадобилось для запуска на платформе, которую Qt не поддерживал.
Вторая причина — доступность Qt на мобильных платформах, или, по крайней мере, на Андроид (насчёт iOS не знаю деталей) достаточно условна. Да, примеры там собираются и работают. А что, если у меня своё приложение со своими Java-UI и нативной библиотекой? Я слабо представляю, как туда вкрутить QtCore. Не говорю, что это невозможно, но не вижу смысла связываться.
За 6 лет работы с системой, написанной по принципу тонкий UI + кросс-платформенное ядро + небольшие велосипеды для недостающей системно-зависимой функциональности, я пришёл к выводу о целесообразности и удобстве такого подхода.
А вот по поводу кроссплатформенности за пределами поддерживаемых систем можно подробнее? Ибо список систем довольно большой и мне интересно где вы не могли Qt использовать.
По поводу Андроида — использовать QtCore в нативной c++ библиотеке вроде не составляет проблемы. Особенно если вы отказываетесь от сигналов/слотов (тогда не надо организовывать Qt-очередь сообщений).
По поводу велосипедов — я принципиально не согласен. Они применимы только в одном случае — требования железа(производительность, ограниченность ресурсов и т.д.). Во всех остальных случаях они нежелательны в том числе по причине проблем с поддержкой. Смениться основной разработчик и будут проблемы.
Сложности могут возникнуть только при необходимости запустить кутешный eventloop, да и то решаемые, в остальном же QtCore такая же подключаемая библиотека как и любая другая, за одним исключением — это хорошо документированное, многофункциональное решение, а не сборная солянка, как тот же boost.
Насчет поддержки платформ — так в Qt есть слой архитектурной абстракции, нужно реализовать не так много компонентов, чтоб библиотека завелась на новой платформе (http://doc.qt.io/qt-5/qpa.html, http://doc.qt.io/qt-5/portingguide.html).
сложность самого фреймворка
Чегоооо? Вы же не серьезно, да?
Qt — это одна из нескольких больших библиотек в мире C++, которую можно начать использовать прям сразу, не вникая в дебри ее построения и мира плюсов вообще. Студенты без серьезного опыта плюсов осваивают либу без особых проблем. О какой сложности идет речь — прям реально любопытно!
вы теряете кросс-платформенность
Чееееееего? Мы вообще про Qt точно говорим?
Qt собирается почти под все девайсы, которые можно найти, включая всякие хитрые микроконтроллеры, отечественные Эльбрусы и прочие смесители от унитаза. Если нужно совсем что-то хитрое, берем близкий конфиг из папки mkspecs, правим его и собираем. Можно узнать, на какую платформу это сделать не получилось?
Я слабо представляю, как туда вкрутить QtCore.
Проинсталлировали вместе с приложением и профит, не?
небольшие велосипеды для недостающей системно-зависимой функциональности
Вы чуть выше писали про велосипеды с сетью и ФС. Это, мягко говоря, не могут быть «небольшие велосипеды», если только вы не делаете эхо-сервер.
Для потоков есть std::thread, для файловой системы был велосипед, теперь жду filesystem в с++17. Слоты/сигналы — очень спорная функциональность, я бы не стал их где-либо за пределами UI и интерфейса ядра для связи с UI, даже если бы они были доступны. В ядре предпочитаю интерфейсы и коллбэки как более понятную и более контролируемую в compile-time связь.
А почему такие жесткие ограничения?
Использование своих велосипедов для работы с сетью вызывает улыбку. Либо у вас сеть только номинально, либо вы очень серьезные сетевики с крутым опытом, либо делаете что-то не то. Сеть в серьезном приложении очень часто является самым узким местом, и изобретать тут велосипеды вместо использования проверенных либ типа boost.asio — фундаментальная ошибка в архитектуре ПО.
с файловой системой у нас свои несложные классы-обёртки
Готов найти не менее трех проблем в ваших несложных обертках.
В итоге, у нас минимум зависимостей от сторонних библиотек
А в чем, собственно, профиты, кроме того, что можно написать «у нас минимум зависимостей от сторонних библиотек»? Какие-то проблемы написать «sudo apt install ...»?
Либо у вас сеть только номинально
Ровно в необходимом объёме. Сокеты, HTTP POST/GET без наворотов.
Готов найти не менее трех проблем в ваших несложных обертках.
Зачем? Всё, что нам нужно, работает на пяти ОС.
Какие-то проблемы написать «sudo apt install ...»?
Какой ещё sudo apt install на iOS?
Какой ещё sudo apt install на iOS?
Ну на целевую платформу все статически собирается. Речь про разработчиков.
Ну на целевую платформу все статически собирается. Речь про разработчиков.
Интересная мысль, но в моём проекте вряд ли больше половины из уже имеющихся сторонних библиотек доступны через apt-get (а особенно — для кросс-компиляции под Андроид / iOS).
std::string path = root_path + '\' + fname;
Данная строка не скомпилируется, так как слэш экранирует кавычку. Исправьте пример пожалуйста (для новичков).
А в нашем случае ситуация была еще сложнее — у нас были большие Windows-приложения и нам нужно было портировать их под POSIX. Огромный объем кодовой базы не позволял нам переписать все за раз — это был длительный итеративный процесс, параллельно с которым шло активное развитие этих продуктов. Как минимум из-за этого нельзя было «сжигать» мосты и переходить на POSIX, отказавшись от WinAPI (иначе на какой-то период сломалась бы сборка на MSVS и работа наших коллег бы встала).
MinGW не решает проблему — он использует WinAPI, ровно как MSVS (сейчас, кстати, мы перешли на него, отказавшись от MSVS). Вы, наверное, имели ввиду cygwin, в котором WinAPI завернут внутрь POSIX-совместимого API.
boost::filesystem::path::preferred_separator
IMHO компилятор ничего не кодирует. Кодировка зависит от кодировки ОС, которая в Linux _обычно_ UTF-8, а в Windows (RU) в GUI-редакторах — CP1251.
А как, интересно, задается кодировка для "конкретного файла"? Есть, конечно, Byte Order Marks, но это только для Unicode.
Например, если в коде есть такая строка, записаная в cp1251:
const char * str = "Какая-то строка";
А далее идет такой код:
std::locale::global(std::locale("C"));
std::cout << str << std::endl;
То выведется кракозябра.
#include <stdio.h>
int main()
{
printf( "%hhu\n", static_cast<unsigned char>( 'я' ) );
printf( "%hu\n", static_cast<unsigned short>( L'я' ) );
}
В случае с Win1251 вы получите ожидаемый результат, который никак не противоречит вашей гипотезе:
255 (это код символа 'я' в Win1251)
1103 (это код символа 'я' в UTF-16, которой представлен wchar_t на Win)
В случае UTF-8 вы получите:
143 (это один из двух байт UTF-8 представления символа 'я' — подходит под вашу гипотезу)
1057 (а это ошибка! компилятор неправильно закодировал L'я', так как неправильно воспринял исходник и сделал перекодировку win1251->utf16 вместо utf8->utf16)
В случае с UTF-8 с заголовком BOM мы имеем результат, идентичный первому варианту:
255
1103
И этот случай противоречит вашей гипотезе — компилятор представил «узкую» строку в 1251, несмотря на то что она была в исходнике в UTF-8.
Таким образом, не все компиляторы берут строки как есть из файла, они их иногда перекодируют.
Годиков X (а то и Y) назад Газпромбанк намутил себе iBank2.
Тогда еще ключи не на токенах, а в файлах.
Ну и вот пробуем с ним работать прямо из Linux (Fedora). С родной JVM не завелось, пришлось ставить Oracle Java for Linux.
И тут всплывают проблемы с кодировкой. Под Linux всё криво. Хотя Java же ж — «Make once — use anywhere» — правда?
А вот отнюдь.
В один из моментов разборок с техподдержкой (банка) порадовали две вещи:
1. «У вас неправильная Java, надо использовать IE и MS JVM и накатить исправление IE насчет MS JVM (это которое приводило MS JVM к „стандарту“ от Oracle)
2. И (это я на всю жизнь запомнил) — »Ваша Java не подходит нашему интернет банку". Моя. Oracle Java. Не подходит к газпробанковскому (точнее — бифит) приложению.
В конце-концов добрался до бифита и там всё починили (буквально за 3 мес. интенсивной переписки — но там люди адекватные оказались).
Но «ваша Java...» — это среди меня теперь мэм.
Хотя Java же ж — «Make once — use anywhere» — правда?Нет.
«У вас неправильная Java, надо использовать IE и MS JVM и накатить исправление IE насчет MS JVM (это которое приводило MS JVM к „стандарту“ от Oracle)Во-первых в те времена Oracle никаких «стандартов Java» ещё не создавал, а во-вторых MS Java — была целенаправленно сделана несовместимой с версий от Sun'а.
И (это я на всю жизнь запомнил) — »Ваша Java не подходит нашему интернет банку". Моя. Oracle Java. Не подходит к газпробанковскому (точнее — бифит) приложению.Думаю что Java у вас была не от Oracla и даже не от Sun'а. Скорее всего это была Blackdown Java — и да она была абсолютно несовместимой с MS Java'ой, причём это не было ошибкой: это была цель, которой Microsoft хотел добиться сознательно.
После того, как им суд погрозил пальчиком они забрали «свои игрушки» и сделали .NET и C#.
В данном цикле статей я расскажу, как мы сделали свои продукты настоящими кроссплатформенными приложениями; как заставили их работать на Linux, MacOS и даже под iOS и Android
Отлично. Но где можно посмотреть эти самые продукты? На сайте https://sbis.ru/download нет ни малейшего упоминания о «кроссплатформенности».
https://play.google.com/store/apps/details?id=ru.tensor.sbis.droid
https://itunes.apple.com/ru/app/%D1%81%D0%B1%D0%B8%D1%81/id1156518720
Остальное — облачные решения;
Облако sbis.ru построено на платформе
Т.е. по сути и статьи и вашего ответа решений под Linux нет?
Облачный СБИС работает не только в IE. И подпись и шифрование работают у нас и в Chrome, и в FireFox.
(Fedora 25, Chromium, если что)
И простому пользователю не Windows из перечисленного ничего не доступно.
и ваш комментарий:
Облачный СБИС работает не только в IE. И подпись и шифрование работают у нас и в Chrome, и в FireFox.
Разговор слепого с немым?
1. У автора статьи сказано, что в wchat_t помещается Unicode символ (я так понимаю подразумевается UTF-32), но на самом деле, целиком умещается только code point, а символ, по прежнему, может кодироваться несколькими code point-ами.
2. В случае сериализации, особенно в рамках ASCII <= 127 получается жуткий перерасход по памяти.
3. В случае сериализации появляется понятие byte order внутри code point-a. Следовательно имеем неразбериху и дополнительные накладные расходы на обработку строк.
4. В рамках исключений С++, даже если очень нужно, невозможно передать локализованной сообщение, так как нет перегрузки для wchar_t.
Преимущества UTF-8:
1. Компактно, при сериализации и по памяти.
2. Нет проблем с порядком байт, можно сразу читать из потока или файла.
3. Парсеры и форматтеры пишутся точно также как и для всех «узких» строк, на практике можно вообще не обращать внимание что строки в UTF8.
4. Удобно смотреть дампы пакетов и памяти, так как, опять же, нет вариантов с порядком байт.
5. И последние, это уже вкусовщина, но мне, лично, удобней и приятней видеть в коде просто кавычки вместо L"..." или L'...'
При вводе нужна постоянная трансформация из code point в позицию в строке и обратно (для показа выделения и позиции ввода).Вы это серьёзно? Как у вас с поддержкой вещей типа а҃? Если же у вас установка, что «в природе существует только русский и английский» — то зачем вообще заморачиваться с Юникодом?
Если нет — либо медленно, либо нужен дополнительный кеш позиции, с нетривиальным алгоритмом обновления при автокомплите.Он, собственно, нужен всегда, когда вы хотите работать с Юникодом.
Конечно, круто делать полную поддержку юникода, с составной диакретикой и поддержкой всех планов, но это далеко не всегда нужно для того, чтобы приложение работало. Это вопрос баланса между желаемой технической крутостью, которую котят разработчики, и минимальным сроком разработки с минимальными затратами, как хотят манагеры.
После заявления что в C++11 нет функционала для работы с сеть задумался, а стоит ли продолжать читать)
То есть понятно, что как бы «работа с сетью» — это, наверное, не есть «базовая функциональность языка программирования»… с одной стороны. А с другой — работа с перфокартами входила в ISA IBM 360, где, вроде как, им тоже не совсем место, да?
Так что не стоит уж прям так сразу «сплеча» рубить…
Я конечно понимаю что грань между тем что называется стандарт языка и стандартная библиотеки языка очень зыбкая, но она всё-таки присутствует. К тому же это не одно спорное заявление в статье. Следом идёт про то что реализация HTTP запроса делает код на С++ не кроссплатформенным. А я например знаю, как реализовать это кросплатформенно. Правда это будет иметь "фатальный недостаток")
А я например знаю, как реализовать это кросплатформенно.Оставаясь в рамках C++11 и не используя не-кроссплатформенные API? Хотел бы я на это посмотреть!
Ну а что вам мешало пойти тем же путем? Впрочем вы с таким восторгом рассказываете о своей платформе, поэтому наверно не стоит даже начинать про «зачем писать свой велосипед?»)
Нет в одиннадцатых плюсах ни поддержки сети, ни поддержки файловой системы. И тут почти всегда выруливает буст.
Я прекрасно вижу разницу между сторонними библиотеками и стандартной библиотекой С++. Или вы что-то другое имели ввиду во фразе «чистый C++11».
Кстати С++11 это номер обновления версии стандарта на язык С++, а не что-то отдельное и самодостаточное. Кстати есть уже и С++14, и даже С++17 — работы с сетью в них тоже нет, и думаю не будет никогда. В философии С++ есть фишка — не тащить прикладной код в стандартную библиотеку.
есть возможность написания строковых констант на русском языке в коде
…
Второе требование в случае UTF-8 не выполняется, к примеру, в MSVC 2010, где строковые константы кодируются в Windows-1251.
Мне кажется, Вы здесь вводите в заблуждение читателей. Строковые константы в UTF-8 прекрасно работают в MSVC 2010. Вам просто нужно превсести все ваши *.h и *.cpp файлы в UTF-8 без BOM и забыть о проблеме с кодировками. Ну и настроить среду так, чтобы новые исходники автоматом создавались в UTF-8.
Кроме этого, если вы реально планируете активно работать с многоязычными текстами, то, на мой взгляд, следовало сразу отказаться от вашего первого требования:
фиксированная ширина символов — возможен произвольный доступ к символу по его индексу
Из-за него вы перешли на UTF-32, а это большой оверхед. При том, что доступ к символам по их индексам на практике — не такая уж и частая задача. Большинство задач, связанных со строками, можно свести к их обходу с помощью итераторов.
By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page
MSVS кодирует «узкие» строки в кодировку локали: https://msdn.microsoft.com/en-us/library/mt589696.aspx
The execution character set has a locale-specific representation.
Таким образом, в случае, если студия компилирует файл в UTF-8, она воспринимает его как файл в кодировке локали. Так как она кодирует узкие строки тоже в кодировку локали, преобразование как таковое не требуется => все ваши UTF-8 строки попадают в исходном виде. Но все это здорово работает, пока в файле не встретится код, не существующий в кодировке локали (это 0x98 в случае русскоязычной локали, где кодировка Win1251).
Кроме того при сборке в MSVS исходников в UTF-8 появляются проблемы с широкими строками — MSVS воспринимает файл как 1251 и выполняет перекодировку Win1251 -> UTF-16 вместо UTF-8 -> UTF-16 и получаем «кракозябры»
Кроме этого, если вы реально планируете активно работать с многоязычными текстами, то, на мой взгляд, следовало сразу отказаться от вашего первого требования:
фиксированная ширина символов — возможен произвольный доступ к символу по его индексу
В статье писалось об этом, но повторюсь — у нас была огромная кодовая база (десятки миллионов строк кода), в этом коде были завязки на фиксированную ширину символов. В принципе переписать этот код — очень тяжелая задача. Но кроме этого, как уже многократно писалось, шло активное развитие продуктов и нужно было портировать таким образом, чтобы не блокировать работу других разработчиков (нельзя было развалить сборку на
Мы пошли путем microsoft'а — все строки завернули в макрос _T( «строка» ), строковым функциям и типам сделали псевдонимы (наподобие _tcscmp). Благодаря этому мы могли жить, поддерживая сразу два варианта сборки: сборка на узких строках (из которой выпускались все production решения) и сборка на широких строках (которая итерационно развивалась в течение года). В случае, если бы мы стали использовать UTF-8, мы не смогли бы обеспечить такую совместимость.
Нет, это не так: https://msdn.microsoft.com/en-us/library/mt708821.aspx
Вы даёте ссылку на документацию к VC 2015, а тут вроде бы речь идёт про 2010. Если бы речь шла про 2015, то проблемы вообще бы не было, просто потому, что у компилятора есть соответствующая опция /utf-8, ссылку на которую Вы и даёте.
MSVS кодирует «узкие» строки в кодировку локали: https://msdn.microsoft.com/en-us/library/mt589696.aspx.
Там же написано следующее
The basic execution character set and the basic execution wide-character set consist of all the characters in the basic source character set...
Ну а про basic source character set сказано так
When source files are saved by using a locale-specific codepage or a Unicode codepage, Visual C++ allows you to use any of the characters of that code page in your source code
Т.е. Microsoft явно разрешает использовать все символы юникода в исходниках.
Вы не подумаете, что я сильно придираюсь, просто хочу понять, есть ли проблема на самом деле и как её воспроизвести. Собрал только что тестовый консольный проект в VC2010, сохранил исходники в utf-8, создал в коде узкую строку на традиционном китайском языке и никаких проблем с ней не обнаружил.
const char* TestString = "測試";
printf("%d", strlen(TestString) );
Этот код у меня на экран консоли выдаёт цифру 6
#include <windows.h>
int main()
{
MessageBoxW(NULL, L"Тест", L"Тест", MB_OK);
}
Результат:
const char* TestString = "測試";
printf("%d", strlen(TestString) );
Кстати говоря, в вашем коде есть неопределенное поведение :) Функция strlen возвращает тип size_t, а формат указан для int. По стандарту это UB:
… If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
Для size_t надо использовать %z
https://connect.microsoft.com/VisualStudio/feedback/details/341454/compile-error-with-source-file-containing-utf8-strings-in-cjk-system-locale
В этом issue люди словили проблемы из-за того что у них в исходнике встретился код, не входящий в кодировку их локали (о чём я писал выше)
Как сделать свой С++ код кроссплатформенным?