Posts Tagged Unix
Сравнение Qt и Java
В этой статье сравнивается эффективность использования C++/Qt и Java/AWT/Swing для разработки программного обеспечения с пользовательским графическим интерфейсом.
1. Что мы сравниваем?
При выборе средств для разработки крупного программного проекта необходимо учесть множество различных аспектов, наиболее важнейшим из которых является язык программирования, потому что он в значительной степени определяет другие доступные средства. Например, для разработки пользовательского графического интерфейса разработчикам необходима GUI-библиотека, предоставляющая готовые элементы интерфейса, такие, как кнопки и меню. Так как выбор GUI-библиотеки оказывает большое влияние на разработку проекта, часто ее выбор осуществляется первым, а язык программирования определяется из числа доступных для этой библиотеки языков. Обычно, язык программирования определяется библиотекой однозначно.
Другие компоненты средств разработки, такие, как библиотеки доступа к базам данных или библиотеки коммуникаций, также должны быть приняты во внимание, но они не оказывают такого влияния на разработку проекта, как библиотеки GUI.
Целью этой статьи является сравнение C++/Qt и Java/AWT/Swing. Чтобы это сделать наиболее точно, мы сначала сравним языки программирования, то есть C++ и Java, а потом две GUI-библиотеки: Qt для C++ и AWT/Swing для Java.
2. Сравнение C++ и Java
Часто при обсуждении преимуществ и недостатков различных языков программирования дебаты сводятся к аргументам, основанным скорее на личном опыте и предпочтениях, чем на объективных критериях. Конечно же, при выборе языка программирования личные предпочтения и опыт разработчика должны быть учтены, но так как эти критерии субъективны, они здесь не принимаются во внимание. Вместо этого мы будем рассматривать продуктивность программирования, производительность работы приложения и эффективность использования памяти, потому что эти критерии могут быть определены количественно и могут быть исследованы с научной точки зрения, хотя мы также учтем информацию, полученную из опыта разработки проектов в нашей компании.
2.1. Продуктивность программирования
Продуктивность программирования определяет, насколько эффективно (т.е. быстро и точно) программист с определенным опытом и знаниями может решить поставленную перед ним задачу, используя заданный язык программирования. Так как оклад разработчика является главной составляющей стоимости разработки любого программного проекта, продуктивность программирования имеет большое значение. Также в определенной степени продуктивность программирования определяется доступными инструментальными средствами.
Отличительной особенностью Java в сравнении с другими языками программирования общего назначения является обеспечение высокой продуктивности программирования, нежели производительность работы приложения или эффективность использования им памяти.
Для этого Java наделена некоторыми дополнительными возможностями. Например, в отличие от C++ (или C), программист не должен в явном виде «освобождать» (возвращать) выделенную память операционной системе. Освобождение неиспользуемой памяти (сборка «мусора») автоматически обеспечивается средой выполнения Java в ущерб производительности и эффективности использования памяти (см. далее). Это освобождает программиста от утомительной задачи по слежению за освобождением памяти – главного источника ошибок в приложениях. Одна эта возможность языка должна значительно увеличить продуктивность программирования в сравнении с C++ (или C).
Однако проведенное исследование показывает, что на практике сборка «мусора» и другие возможности Java не оказывают большого влияния на продуктивность программирования. Одна из классических моделей оценки программного обеспечения CoCoMo, предложенная Barry Boehm, предопределяет стоимость и сроки разработки программного продукта на основе стоимостных коэффициентов, которые учитывают такие факторы, как суммарный опыт программирования разработчика, опыт программирования на заданном языке, желаемая надежность программы и т.д. Boehm пишет, что независимо от уровня используемого языка, начальные трудозатраты всегда высокие. Подобная методика подсчета использовалась в другом исследовании, проведенном C.E.Walston и C.P.Felix, IBM, Метод измерения и оценки программирования (A method of programming measurement and estimation) .
Оба приведенных здесь исследования появились задолго до создания Java, но несмотря на это, они демонстрируют общий принцип: сложность языка программирования общего назначения по сравнению с другими аспектами, такими как квалификация разработчика, не оказывает существенного влияния на полную стоимость разработки проекта.
Существует более позднее исследование, которое явно включает Java и которое подтверждает эту гипотезу. В Эмпирическом сравнении C, C++, Java, Perl, Python, Rexx и Tcl (An empirical comparison of C, C++, Java, Perl, Python, Rexx, and Tcl) Lutz Prechelt из университета Karlsruhe описывает проведенный им эксперимент, в котором студентам информатики поручили выполнить определенный проект и выбрать для его реализации, руководствуясь личными предпочтениями, один из языков программирования: C, C++ или Java (остальные языки были рассмотрены в другой части исследования). Собранные данные показали почти одинаковые результаты для C++ и Java (C был на третьем месте по многим параметрам). Эти результаты подтверждаются нашим собственным опытом: если программисты вольны в самостоятельном выборе языка программирования (чаще руководствуясь при этом своим опытом), программисты с равным опытом работы (например, измеренным в годах) достигают одной и той же продуктивности. Второй интересный аспект, который бы мы хотели отметить (но который не имеет формального экспериментального подтверждения), заключается в том, что менее опытные разработчики достигают лучших результатов с Java, разработчики со средним опытом разработки достигают одинаковых результатов с обоими языками программирования, опытные разработчики достигают лучших результатов с C++. Эти наблюдения могут быть объяснены тем, что для C++ доступны более совершенные средства разработки; и этот факт тоже должен быть принят во внимание.
Интересный способ определения продуктивности программирования предлагает метод функциональных единиц (Function Point), разработанный Capers Jones. Функциональная единица – это метрика программного обеспечения, которая зависит лишь от его функциональности, а не от конкретной реализации. Эта метрика позволяет использовать в качестве критерия оценки продуктивности программирования число строк кода, необходимых для обеспечения одной функциональной единицы, в свою очередь, уровень языка определяется числом функциональных единиц, которые могут быть созданы за определенное время. Интересно, что обе величины: число строк кода на единицу функциональности и уровень языка одинаковы для обоих языков (уровень языка: C++ и Java – 6, C – 3.5, Tcl – 5; число строк кода на единицу функциональности: C++ и Java – 53, C – 91, Tcl – 64).
Подводя итог: оба исследования и практика опровергают утверждение, что Java обеспечивает программистам лучшую продуктивность программирования, нежели C++.
2.2. Производительность работы приложений
Мы увидели, что преимущества продуктивности программирования на Java оказались иллюзорными. Теперь мы исследуем производительность работы приложений.
И снова Prechelt предоставляет интересные сведения. Объем предлагаемой им информации огромен, но в конечном итоге он приходит к заключению, что «Java-программы выполняются по крайней мере в 1.22 раза медленнее C/C++ программ». Заметьте, что он сказал по крайней мере; средняя же скорость работы Java-программ гораздо меньше. Наш собственный опыт показывает, что Java-программы выполняются приблизительно в 2-3 раза медленнее своих C/C++ аналогов. На задачах, ориентированных на интенсивное использование процессора, Java-программы проигрывают еще сильнее.
В случае программ с пользовательским графическим интерфейсом увеличение времени отклика интерфейса является более критичным, чем низкая производительность программы. Проведенные исследования показывают, что пользователи более терпимы к задачам, выполняющимся в течение двух или трех минут, чем к программам, которые не реагируют мгновенно на их воздействия, например, на нажатия кнопок. Эти исследования показывают, что если время отклика программы больше, чем 0,7 секунды, пользователи считают ее медленной. Мы вернемся к этой проблеме, когда будем сравнивать пользовательский графический интерфейс в программах Java и C++.
Объяснение того, почему Java-программы медленнее C++ проограмм, заключается в следующем. C++ программы компилируются компилятором C++ в двоичный формат, который затем исполняется непосредственно процессором; таким образом, выполнение программы осуществляется аппаратными средствами. (Это несколько упрощенно, так как большинство современных процессоров выполняют микрокод, но это не принципиально при обсуждении данного вопроса.) С другой стороны, компилятор Java компилирует исходный код в «байт-код», который непосредственно исполняется не процессором, а с помощью другого программного обеспечения, виртуальной машины Java (Java Virtual Machine, JVM). В свою очередь, JVM исполняется процессором. Таким образом, выполнение байт-кода Java-программ осуществляется не быстрыми аппаратными средствами, а с помощью более медленной программной эмуляции.
Для повышения производительности работы Java-программ были разработаны «Just in Time» (JIT) компиляторы, но универсального решения этой проблемы не существует.
На первый взгляд, полуинтерпретируемая природа Java-программ обеспечивает выполнение принципа «скомпилированный однажды код выполняется везде». Однажды скомпилированная в байт-код Java-программа может выполняться на любой платформе, для которой доступна JVM. На практике же, это не всегда так из-за отличий в реализациях разных JVM и из-за необходимости иногда наряду с Java-программами использовать родной, не-Java код, обычно написанный на C или C++.
Но разве использование платформенно-независимого байт-кода является верным подходом в создании кросс-платформенных приложений? С хорошим кросс-платформенным инструментарием, наподобие Qt, и хорошими компиляторами для различных платформ программисты могут достичь почти той же цели компиляцией своего исходного кода один раз для каждой из платформ: «написанный однажды код компилируется везде». Можно возразить, что для этого разработчикам потребуется доступ ко всем поддерживаемым платформам, в то время, как с Java, теоретически, разработчикам необходим доступ только к одной из платформ, имеющей средства разработки для Java и JVM. На практике же ни один из ответственных производителей программного обеспечения не будет сертифицировать свои программные продукты для платформ без предварительного их тестирования, поэтому в любом случае производителям будет необходим доступ ко всем поддерживаемым платформам.
Возникает вопрос, зачем использовать программную реализацию виртуальной машины Java, если такую же функциональность можно получить с помощью аппаратной реализации? Именно так рассуждали разработчики при создании языка Java; они предполагали, что вопрос низкой производительности будет решен, когда станет доступной аппаратная реализация JVM в виде Java-процессоров. Однако даже по прошествии пяти лет Java-процессоры не получили широкого распространения. Существуют проектные экземпляры и даже работающие прототипы Java-процессоров, однако понадобится еще немало времени, чтобы стало возможным их приобрести.
2.3. Эффективность использования памяти
Java и C++ используют различные подходы в управлении памятью. В C++ управление памятью полностью осуществляется программистом, т.е. по мере необходимости распределение и освобождение памяти должно выполняться программистом. Если программист забывает освободить ранее полученную память, возникает «утечка памяти». Если во время работы приложения произойдет лишь одна такая утечка, проблем не возникнет, так как после завершения работы приложения операционная система освободит всю ранее использованную им память. Но если утечки памяти будут происходить постоянно (например, если пользователь будет периодически выполнять определенные действия), использование памяти приложением будет расти вплоть до полного ее расхода с последующим возможным отказом системы.
Java обеспечивает автоматическое освобождение неиспользуемой памяти. Наряду с распределением памяти программистом JVM ведет учет всех используемых блоков памяти и указателей на них. Если блок памяти больше не используется, он может быть освобожден. Это обеспечивает процесс, который называется «сборкой мусора». Он периодически вызывается JVM, проверяет все используемые блоки памяти и освобождает те из них, на которые отсутствуют указатели.
Сборка мусора очень удобна, но за ее использование приходится расплачиваться большим потреблением памяти и низкой произодительностью… Программисты C++ могут (и должны) освобождать блоки памяти сразу после того, как они перестали быть нужны. С Java блоки не освобождаются до очередного вызова сборщика мусора, периодичность работы которого зависит от использумой реализации JVM. Prechtelt предоставляет цифровые данные, утверждая, что в среднем, (…) и с вероятностью 80% Java-программы используют на 32 MB (или 297%) памяти больше, чем C/C++ программы (…). Вдобавок к большому расходу памяти процесс сборки мусора требует дополнительной процессорной мощности, которая в результате становится недоступной приложению, и это приводит к замедлению его работы. Поэтому периодическая работа сборщика мусора может приводить к «замораживанию» Java-программы на несколько секунд. Лучшие реализации JVM минимизируют такие замораживания, но не устраняют их полностью.
При работе с внешними программами и устройствами, например, во время ввода/вывода или при взаимодействии с базой данных, желательно закрыть файл или соединение с базой данных сразу же после того, как они перестали быть нужны. Благодаря деструкторам C++ это происходит сразу после вызова delete. В Java закрытие произойдет лишь во время следующего цикла работы сборщика мусора. В лучшем случае это может привести к излишней блокировке ресурсов, в худшем – к нарушению целостности открытых ресурсов.
Тот факт, что Java-программы используют блоки памяти большие, чем необходимо, является особенно критичным для встраиваемых устройств, объемы памяти которых невелики. Неслучайно, что до сих пор (на время написания этой статьи) не существует полной реализации Java-платформы для встраиваемых устройств, а лишь ее частичные реализации.
Главная причина, по которой сборка мусора является более дорогостоящей, чем непосредственное управление памятью программистом, – это утрата информации. В C++ программе программист знает и местонахождение своих блоков памяти (сохраняя указатели на них), и когда они перестанут быть ему нужными. В Java-программе последняя информация недоступна для JVM (даже если она известна программисту), поэтому JVM должна перебирать все блоки на предмет отсутствующих указателей. Для того, чтобы вызвать сборку мусора вручную, Java-программист может удалить все указатели на больше ненужные ему блоки памяти. Но со стороны программиста это потребует больше усилий, чем непосредственное управление памятью в C++; и тем не менее, во время сборки мусора JVM все равно придется проверить все блоки памяти, чтобы освободить неиспользуемые.
С технической точки зрения, нет ничего такого, что бы мешало реализовать сборку мусора в C++ программах. Существуют обеспечивающие это коммерческие программы и библиотеки. Но из-за перечисленных выше недостатков немногие C++ программисты используют их. Инструментарий Qt использует более эффективный подход для упрощения задачи управления памятью: при удалении объекта все зависящие от него объекты также автоматически удаляются. Подход Qt не мешает программистам по желанию самостоятельно удалять объекты.
Так как управление памятью в C и C++ обременительно для программиста, созданное с помощью этих языков программное обеспечение обвиняется в нестабильной работе и подверженности ошибкам. Хотя некорректная работа с памятью в C и C++ может привести к более критичным ошибкам (обычно приводящим к аварийному завершению программы), хорошие знания, инструментарий и опыт могут значительно уменьшить связанный с этим риск. Изучению управления памятью должно уделяться достаточно внимания. Также существует большое число коммерческих и свободных инструментов, позволяющих программистам обеспечить отсутствие в программах ошибок при работе с памятью; например, Parasoft Insure++, Rational Purify и Electric Fence. Гибкая система управления памятью в C++ делает возможным создавать адаптированные для любого типа приложений профилировщики памяти.
В результате этого обсуждения мы убедились в том, что при сравнимой продуктивности программирования C++ обеспечивает приложениям гораздо лучшие, чем Java, производительность работы и эффективность использования памяти.
2.4. Доступные библиотеки и инструментарий
Java-платформа предлагает внушительное число пакетов, насчитывающих сотни классов для любых задач, включая пользовательский графический интерфейс, безопасность, поддержку сети и прочие. Это несомненное преимущество Java-платформы. Любому Java-пакету соответствует, как минимум, одна C++ библиотека, хотя иногда бывает очень трудно собрать в одном C++ проекте множество библиотек и заставить их вместе правильно работать.
Однако это преимущество Java является также ее недостатком. Разобраться в огромном API программисту становится все сложнее. Можете быть наверняка уверены, что для любой задачи всегда найдется уже готовое решение или, по крайней мере, решение, облегчающее выполнение этой задачи. Но найти пригодный для этого пакет и класс становится все труднее. Также с увеличением числа пакетов стремительно растет размер Java-платформы. В результате стали возникать ее «урезанные» версии, утратившие преимущества готовых решений. Таким образом, размер Java-платформы делает практически невозможным создание Java-систем небольшими производителями независимо от создателя Java Sun Microsystems, что ослабляет конкуренцию.
Если преимущества Java заключаются в доступных библиотеках, то явные преимущества C++ – в имеющихся средствах разработки. За все время существования семейства языков C и C++ было создано огромное количество самых разнообразных средств разработки, включая инструменты для дизайна, отладки и профилирования. Имеющиеся средства разработки для Java часто уступают по возможностям своим C++ -аналогам. Это справедливо даже для инструментов от одного производителя; например, сравните Java и C/C++ -версии профилировщика Rational Quantify.
Самым важным инструментом для любого разработчика, использующего компилируемые языки, является компилятор. Основным достоинством компиляторов C++ является скорость работы. Для обеспечения кросс-платформенности своих компиляторов (и других средств разработки) производители Java-инструментов часто сами используют Java, со всеми вытекающими отсюда проблемами производительности и эффективности использования памяти. Иногда встречаются Java-компиляторы, написанные на C/C++ (например, IBM Jikes), но это редкость.
3. Сравнение AWT/Swing и Qt
До сих пор мы сравнивали лишь языки программирования Java и C++. Но, как мы упомянули в начале этой статьи, язык программирования является лишь одним из аспектов, принимаемых во внимание при разработке GUI. Теперь мы сравним пакеты для разработки GUI, которые поставляются вместе с Java, т.е. AWT и Swing, и кросс-платформенный инструментарий Qt от норвежского производителя Trolltech. В сравнении мы ограничились лишь одним инструментарием C++, потому что в отличие от MFC (Microsoft Foundation Classes) и других подобных библиотек, Qt поддерживает все 32-битные Windows-платформы (кроме NT 3.5x), большинство разновидностей Unix, включая Linux, Solaris, AIX и Mac OS X, и встраиваемые платформы. Это позволяет максимально близко сопоставить платформы Java/AWT/Swing и C++/Qt.
3.1. Описание AWT, Swing и Qt
Инструментарий AWT (Abstract Windowing Toolkit) начал поставляться с самой первой версией Java. Он использует родные для платформ компоненты GUI (т.е. Win32 API для Windows и библиотеку Motif для Unix), обеспечивая таким образом переносную обертку. Это значит, что внешний вид и поведение AWT-программ будет отличаться на различных платформах, потому что именно они занимаются отрисовкой и управлением компонентов GUI. Это противоречит кросс-платформенной философии Java и может быть объяснено тем, что первая версия AWT была разработана за четырнадцать дней.
По этой и другим причинам AWT был дополнен инструментарием Swing. Swing использует AWT (и, следовательно, низкоуровневые библиотеки) только лишь для базовых операций: создания прямоугольных окон, управления событиями и отрисовки графических примитивов. Всем остальным, включая отрисовку компонентов GUI, занимается Swing. Это решает проблему отличающегося внешнего вида и поведения приложений на различных платформах. Но из-за реализации Swing-инструментария средствами Java его производительность оставляет желать лучшего. В результате Swing-программы медлительны не только во время интенсивных вычислений, но и при отрисовке элементов пользовательского интерфейса. Как уже говорилось, ничто не вызывает у пользователей такого раздражения, как большое время отклика интерфейса программ. Странно наблюдать за медлительностью перерисовки Swing -кнопки на современном оборудовании. Хотя с ростом производительности оборудования эта ситуация будет постепенно улучшаться, сложным пользовательским интерфейсам, созданным с помощью Swing, всегда будет свойственна медлительность.
При разработке инструментария Qt был использован тот же самый подход: низкоуровневые библиотеки используются только лишь для базовых операций, а отрисовкой элементов GUI занимается непосредственно Qt. Благодаря этому инструментарий Qt приобретает все преимущества Swing (например, схожесть поведения и внешнего вида приложений на различных платформах), и не имеет проблем, связанных с низкой производительностью, так как разработан на C++ и откомпилирован в машинный код. Интерфейс, созданный с помощью Qt, отличается быстрой работой, и, благодаря использованию кеширования, может быть быстрее интерфейса, разработанного стандартными средствами. Теоретически, оптимизированная не-Qt программа должна быть быстрее аналогичной Qt-программы; но на практике для оптимизации не-Qt программы потребуется больше усилий и мастерства, чем для создания оптимизированной Qt-программы.
И Qt, и Swing поддерживают технику стилей, которая позволяет программам независимо от платформы использовать один из стилей интерфейса. Это становится возможным благодаря тому, что отрисовкой элементов GUI занимаются непосредственно Qt и Swing. Вместе с Qt поставляются стили, которые эмулируют внешний вид Win32, Motif, MacOS X Aqua (в Macintosh-версии), и даже стиль, эмулирующий внешний вид Swing-программ.
3.2. Парадигмы программирования в Qt и Swing
Несмотря на то, что оценка API в определенной степени является делом личных предпочтений программиста, среди API-интерфейсов можно выделить такие, которые сделают ваш код более простым, кратким, элегантным и читаемым, чем другие. Ниже мы приводим два примера кода: первый с использованием Java/Swing, а второй с использованием C++/Qt, в которых реализуется вставка нескольких элементов в иерархическое дерево. Swing-код:
...
DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Root" );
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode( "Child 1" );
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode( "Child 2" );
DefaultTreeModel model = new DefaultTreeModel( root );
JTree tree = new JTree( model );
model.insertNodeInto( child1, root, 0 );
model.insertNodeInto( child2, root, 1 );
...
Этот же код с использованием Qt:
...
QListView* tree = new QListView;
QListViewItem* root = new QListViewItem( tree, "Root" );
QListViewItem* child1 = new QListViewItem( root, "Child 1" );
QListViewItem* child2 = new QListViewItem( root, "Child 2" );
...
Как видите, Swing использует архитектуру Model-View-Controller (MVC), в то время как Qt ее поддерживает, но не навязывает использовать. Поэтому Qt-код более интуитивен. К такому же результату приводит сравнение кода для создания заполненной таблицы или других сложных компонентов GUI.
Вторым интересным моментом является то, как различные инструментарии связывают воздействие пользователя (например, выбор элемента в выше созданном дереве) с определенной функциональностью (вызовом функции или метода). Синтаксически в Java/Swing и C++/Qt это выглядит по-разному, но основной принцип общий. Трудно сказать, какой код является более ясным и элегантным, Swing-код:
...
tree.addTreeSelectionListener( handler );
...
или Qt-код:
...
connect( tree, SIGNAL( itemSelected( QListViewItem* ) ),
handler, SLOT( handlerMethod( QListViewItem* ) ) );
...
С одной стороны, Swing-код выглядит проще, а с другой – Qt-код более гибок. Qt позволяет программисту использовать для управляющей функции любое имя, в то время, как Swing обязывает использовать в качестве имени valueChanged() (вот почему в приведенном выше Swing-примере оно не было указано явно). Также Qt позволяет связывать событие (сигнал в терминологии Qt) с любым числом управляющих функций (слотов).
Таким образом, и Java/AWT/Swing, и C++/Qt одинаково хорошо подходят для разработки сложного пользовательского интерфейса. Главным недостатком Swing-интерфейса является низкая производительность Java.
4. Заключение
Мы сравнили две платформы: Java/AWT/Swing и C++/Qt, оценив их пригодность для эффективной разработки высокопроизводительных приложений с пользовательским графическим интерфейсом. В то время, как Java-платформа обеспечивает разработчикам сравнимую продуктивность программирования, платформа C++/Qt обеспечивает приложениям лучшую производительность и эффективность использования памяти. Также C++ выигрывает за счет более лучших средств разработки.
Что касается сравнения GUI-библиотек, Swing и Qt, то видно, что более худшая производительность Java-программ делает платформу Java/Swing менее подходящей для разработки GUI-приложений, даже при сравнимом опыте программирования. В отличие от Swing, Qt не навязывает программисту парадигму программирования Model-View-Controller, поэтому в результате Qt-программы получаются более краткими.
Независимое научное исследование и полученный практический опыт эксплуатации показывают, что предпочитаемость использования Java во многих случаях чаще всего неоправданна, а комбинация C++/Qt является более лучшей. Главными причинами этого являются более низкие производительность и эффективность использования памяти в Java (особенно при использовании инструментария Swing) при такой же обеспечиваемой продуктивности программирования. Во многих выполненных нами проектах начинающие программисты осваивали быстрее Java, более опытные и профессиональные разработчики (занимающиеся проектированием приложений и реализацией критичных участков программ) достигали быстрее лучших результатов с помощью C++.
Java/Swing может подойти для разработки некоторых программ, особенно если они без GUI-интерфейса или с ограниченной GUI-функциональностью. В целом, C++/Qt является более лучшим решением, в особенности для разработки GUI-приложений.
Источник: Сравнение Qt и Java
Сравнение MFC и Qt
После размещения этой статьи в интернете, она получила следующую критику :
- Она написана не достаточно хорошо
- Плохо описаны проблемы MFC
- Нет примеров кода
- Статья не объективна, так как Qt постоянно хвалят
- Автор не проявляет глубоких знаний MFC и представляет все в ложном свете
- Автор не сравнивает Qt с .NET
Хочу ответить, что написать хорошую статью не просто. Для этого потребовалось бы очень много времени, проверенных фактов, примеров кода и сравнений. Если бы у меня была такая хорошая статья, то я, вероятно, опубликовал бы ее на более профессиональных сайтах, а не на своей домашней странице.
В какой-то степени я признаю эту критику. Изначально эта статья задумывалась как небольшой обзор по программированию с помощью MFC. А в результате в ней отражено больше проблем, с которыми мы столкнулись при программировании с MFC, чем простого сравнения Qt и MFC. Но критика принимается, и моя следующая статья будет лучше.
И все же, я думаю, что представленный ниже материал по-прежнему ценен. Я не встречал ни одного сравнения MFC и Qt. Поэтому это единственное.
Philippe Fremy
Введение
Я программирую используя Qt и MFC. Поэтому мне хотелось бы поделиться с Вами своим опытом использования этих двух инструментариев.
Я не пишу профессионально. Поэтому эта статья не так хороша и приятна как те, которые Вы найдете на профессиональных веб-сайтах или в журналах. Это всего лишь мой опыт, который я передаю Вам своими словами. Английский – не родной мне язык, поэтому мои конструкции, вероятно, немного странны и в них есть ошибки. Пожалуйста, сообщите мне о них, чтобы я мог их исправить.
Эта статья не претендует на объективность. Она – лишь результат моего личного опыта. Я не перечисляю в ней все хорошие и плохие особенности Qt и MFC. Тот факт, что мое знакомство с Qt произошло раньше, чем я стал программировать с MFC, мог также повлиять на мою объективность.
Эта статья написана с прагматической точки зрения: мой босс предоставляет мне спецификацию приложений, которые он хочет, а я разрабатываю их. Для разработки одних я использовал Qt, для других – MFC.
Microsoft Foundation Class (MFC) – это графический инструментарий для операционных систем Windows. Это более или менее объектно-ориентированная обертка (wrapper) для интерфейса прикладного программирования (API) win32. Предоставляемый инструментарием MFC API имеет ужасный смешанный C/C++-интерфейс.
Qt – это графический C++ -инструментарий, который разрабатывается с 1994 года Trolltech (www.trolltech.com). Он доступен для Windows (любой версии), Unix (любой версии), MacOS X и встроенных устройств, таких как Sharp Zaurus. Несомненно, инструментарий Qt полностью объектно-ориентирован.
Модель Document/View
Инструментарий MFC требует от программиста использования модели Document/View и шаблонов. Почти невозможно не использовать их. Но шаблоны имеют фиксированную структуру, и поэтому очень сложно расширить их возможности. Например, невозможно разделить область и отобразить два вида двух различных документов. Вторая проблема заключается в том, что шаблон создает вид, но не имеет к нему доступа. Все должно быть сделано документом, и это иногда создает проблемы.
Qt не ограничивает Вас какой-либо моделью дизайна. Без каких-либо ограничений Вы можете использовать модель Document/View, если сочтете это нужным. Или не использовать ее.
Сравнение псевдо-объектной и хорошей объектной архитектур
Главное отличие между Qt и MFC – в их дизайне.
MFC – своего рода объектная обертка к Windows API, который написан на C. Это сложно назвать хорошим объектно-ориентированным дизайном. В различных местах Вы должны будете снабжать C-структуру 15-тью членами, из которых только один будет релевантным. Или должны будете вызывать функции с устаревшими параметрами.
Также встречаются различного рода уловки, лишенные какой-либо логичности. Например, если Вы создаете графический объект, он не создастся, пока не будет вызван метод Create(). Для диалогов Вы должны ожидать OnInitDialog(), а для видов Вы ожидаете OnInitialUpdate(), … Также, если Вы создаете объект вручную и вызываете его методы, возникают сбои.
Давайте для примера возьмем диалог, содержащий элемент управления CEdit. Вы не можете для него использовать метод GetWindowText() вне метода DoModal(). Это приведет к сбою. Почему текст элемента управления можно получить только тогда, когда он находится в определенном состоянии? Подобных уловок в MFC полно. К тому же, это усложняет отладку.
Qt – полная противоположность. Ее объектно-ориентированная архитектура, очевидно, была хорошо продумана. Поэтому инструментарий имеет согласованное именование, наследование, организацию классов и методов. Количество аргументов методов ограниченно только необходимыми. Их порядок постоянен для различных классов. А возвращаемое значение логично. И в тоже время сохраняется функциональность и простота. Использовав однажды один из классов, Вы сможете использовать и другие, потому что они работают сходным образом.
Чтобы в Qt получить элемент управления Edit, Вы должны создать объект QLineEdit с помощью оператора new, как в обычных классах C++. Тотчас же Вы получаете доступ ко всем методам объекта, независимо от того, отображен он или нет. Это работает самым простым образом, как только Вы можете себе представить.
Цикл сообщений
Работа MFC основана на механизме сообщений. Чтобы что-то сделать, Вы должны обработать определенное сообщение. Windows посылает программе тысячи сообщений. К сожалению, непросто узнать, как их использовать, какую информацию они передают, когда они посылаются. Некоторые из них излишни, некоторые не посылаются, некоторые не документированы. В документации эта тема не очень хорошо описана. Непросто узнать, какой объект что и когда посылает, какой объект получает или не получает, и какое действие было выполнено. Некоторые возможности, доступные через сообщения, предпочтительнее было бы реализовать через прямые вызовы. Механизм сообщений усложняет отладку и чтение листингов.
Работа Qt базируется на механизме обратного вызова, основанном на передаче сигналов и их приеме слотами (Slots). Эта система является основным механизмом связи между объектами. Сигнал может передавать любое число аргументов. Это очень удобно. Необходимые сигналы Вы подключаете к соответствующим слотам, так что в итоге Вы всегда знаете, что происходит. Число сигналов, передаваемых классом, обычно невелико ( 4 – 5 ), и все они очень хорошо документированы. Этот процесс находится под полным Вашим контролем. Механизм использования сигналов и слотов приближенно напоминает Java listener, но он более легковесен и универсален.
Создание интерфейса
MFC не обеспечивает компоновку (layout) элементов управления внутри окна: это создает проблемы при желании сделать окно изменяемого размера. Вы должны вручную перемещать элементы управления при изменении размеров окна (это объясняет, почему большинство диалоговых окон Windows неизменяемого размера). Эта проблема приобретает большее значение для программ, интерфейс которых должен быть переведен на язык с более длинными словами или предложениями. Вы должны будете перекомпоновать Ваши окна для каждого такого языка.
Редактор ресурсов Visual Studio весьма ограничен: Вы можете расположить элементы управления в фиксированных позициях и только. Некоторые свойства могут быть откорректированы, а мастер класса позволит Вам легко создать переменные и методы. Однако стоит заметить, что создать вручную цикл сообщений, функцию DDX ( Dialog Data Exchange ) или макрос IMPLEMENT_DYNCREATE будет достаточно сложно.
С Qt все можно сделать вручную, так как это несложно. Чтобы получить кнопку, Вам достаточно написать:
button = new QPushButton( "button text", MyParentWidget);
Если Вы хотите, чтобы при нажатии на кнопку был выполнен метод action(), Вам следует написать следующее:
connect( button, SIGNAL( clicked() ), SLOT( action() ) );
Qt имеет гибкий механизм компоновки, который является настолько простым, что Вы тратите впустую время, если его не используете.
Для создания пользовательского графического интерфейса Qt предлагает инструмент Qt Designer. С помощью него Вы можете откорректировать любые свойства используемых элементов управления. Вы не фиксируете жестко их расположение, а используете компоновщик (layout manager). Также Вы можете подключить сигналы к слотам. Это делает Qt Designer большим, нежели обычный дизайнер интерфейса. Он генерирует код, который Вы фактически можете читать и понимать. Поскольку сгенерированный код сохраняется в отдельном файле, Вы можете изменить его в любой момент.
Qt Designer позволяет Вам делать то, что невозможно в MFC, например, создать список с предзаполненными полями или использовать различного вида вкладки (tab controls).
Документация
Если Вы желаете использовать богатый графический инструментарий, то его документация для Вас будет иметь большое значение. Документация MSDN (которую Вы должны будете оплатить отдельно) огромна – на 10 CD-ROM. Она охватывает множество разнообразных тематик. Однако возникает ощущение, что она плохо отобрана и этим представляет меньшую ценность. Перекрестные ссылки также очень бедны. Трудно переходить к базовым, родственным классам и классам-потомкам. Методы представлены без сигнатуры. Трудно обратиться к унаследованным методам класса. Другая проблема состоит в том, что если Вы производите поиск по ключевому слову, то получаете результат для VC++, Visual Basic, Visual J++ и InterDev, даже если используете фильтр.
Документация Qt просто превосходна. Лучше взгляните сами: doc.trolltech.com
Она охватывает все области применения Qt и занимает 18 Мбайт. Каждый класс и метод подробно документирован с множеством примеров использования. Удобная навигация между классами и методами осуществима любым веб-браузером или инструментом, предлагаемым Trolltech (Qt Assistant). Вместе с документацией поставляются руководство и примеры использования. Также имеется FAQ и список рассылки, доступный через группы новостей и веб-интерфейс с возможностью поиска. Если у Вас есть лицензия, Вы поможете воспользоваться технической поддержкой, обычно реагирующей в течение одного дня.
Хорошая продуманность Qt уменьшает потребность в посторонней помощи. Одно из заявлений Trolltech гласит: «Продукт и документация должны быть настолько хороши, чтобы не возникало потребности в технической поддержке».
Юникод
Чтобы отобразить юникод в MFC, Вы должны скомпилировать и собрать приложение со специальными опциями (а также сменить точку входа исполняемого модуля). Вы должны добавить _T к каждой строке, используемой в программе, и сменить тип ‘char’ на TCHAR. Все функции, обрабатывающие строки (strcpy, strdup, strcat, …), должны быть заменены другими. Самое досадное то, что программа, откомпилированная с поддержкой юникода, не будет работать с библиотеками DLL, откомпилированными без его поддержки. Если при разработке Вы используете сторонние библиотеки DLL, это может доставить Вам серьезные неудобства.
Строки в Qt являются объектами класса QString, который изначально поддерживает юникод. Поэтому Вам не придется изменять свой код или использовать какие-либо опции при компиляции и сборке. Просто используйте QString. Qt изначально поддерживает юникод, поэтому здесь не будет никаких проблем.
Сам по себе класс QString очень функционален, даже если Вы и не заботитесь об юникоде. Поэтому поддержка юникода достигается весьма просто. В случае необходимости QString может обеспечить перевод строки в char* или UTF8.
Большое различие между строковым классом MFC CString и QString заключается в их дизайне. CString основан на типе char* с несколькими методами. Преимущество такого подхода в том, что везде, где требуется переменная типа char*, Вы можете использовать член класса CString. Однако это привлекательно только на первый взгляд. Большим недостатком такого подхода является то, что Вы можете изменить член char* класса CString без обновления самого объекта класса. Также возникают трудности при преобразовании в юникод.
QString, напротив, содержит юникод-версию строки и обеспечивает тип char* только когда требуется. API Qt в качестве строчных аргументов всегда требует QString, поэтому Вам редко придется использовать тип char*. Класс QString также обладает некоторыми дополнительными возможностями, вроде совместного использования (или копирования по требованию) содержимого строки. Поэтому Вам наверняка захочется его использовать в своих программах. Это типичный пример хорошей архитектуры Qt и С++ -оберточной MFC.
Локализация
MFC-программу можно локализировать. Для этого Вы должны поместить каждую строку в строковую таблицу и повсюду в Вашем коде использовать функцию LoadString( IDENTIFIER ). Затем Вы должны поместить строковую таблицу в библиотеку DLL. Используя Visual Studio, перевести строки в желаемый язык, преобразовать графические ресурсы (потому что их текст не может быть помещен в строковую таблицу) и использовать эту библиотеку в программе. Это настолько сложно, что Вы, вероятно, не сможете доверить эту работу одному переводчику, ему нужна будет Ваша помощь. У Вас также могут возникнуть проблемы из-за фиксированного позиционирования элементов управления в MFC. Так как их расположение определялось длиной непереведенных строк, более длинные переведенные фразы будут накладываться. Если в последствии Вы поменяете некоторые строки или добавите новые, Вы должны будете убедиться, что перевод был обновлен.
В Qt Вы всего лишь передаете ваши строки функции tr() . Это очень удобно при разработке – Вы можете изменять строки непосредственно в Вашем коде. Специальная программа Qt Linguist извлечет все требующие перевода строки и в удобном виде их отобразит. Ее интерфейс обеспечивает удобный перевод, возможность использования словаря, просмотр контекста строки, обнаружение конфликта клавиатурных сокращений, обнаружение новых непереведенных или измененных строк. Эта программа может использоваться переводчиком без познаний в области разработки программ. Редактор Qt Linguist доступен по лицензии GPL, поэтому Вы можете его модифицировать. Перевод сохраняется в формате XML, поэтому он может быть легко использован в различных целях. Задача добавления к программе нового перевода заключается в создании нового файла с помощью Qt Linguist.
Проблемы ресурсов приложений
Процесс разработки с помощью MFC тесно связан с созданием ресурсов приложений. Они используются во многих случаях. Это имеет следующие последствия:
- Практически невозможно заниматься разработкой программ с помощью средств, отличных от Visual Studio.
- Редактор ресурсов весьма ограничен в своих возможностях. Например, сделать абсолютно все с помощью редактора диалогов невозможно: некоторые свойства могут быть изменены, а некоторые нет. Так, для панелей инструментов Вы вынуждены использовать изображение, которое содержит изображения всех кнопок. Чтобы приложению дать название, Вы должны поместить некоторые строки в строковую таблицу, но Вы должны знать, что каждое поле отделяется символом ‘\n’. Порядок и значение каждого поля не очевидны, и их нелегко определить.
- В файле «Resource.h» идентификаторам ресурсов присваиваются значения (числа, меньшие 32768). Это создает проблемы при удалении или переименовании ресурсов, а также в случае, когда различные библиотеки DLL используют файлы «resource.h» с одинаковыми названиями ресурсов, но с различными их значениями (типичный случай при использовании DLL-компонент).
- При использовании библиотеки DLL с ее собственными ресурсами, которая, в свою очередь, использует другие DLL-библиотеки, возникает вероятность, что программа может смешать свои ресурсы с ресурсами подключаемых библиотек, так как ресурсы могут иметь одинаковые значения идентификаторов. Поэтому Вы должны зарезервировать для своих ресурсов диапазон уникальных значений. Но это не всегда возможно, так как библиотеки могут быть для Вас неподконтрольны.
Отсутствие концепции ресурсов в Qt решает все вышеперечисленные проблемы. Qt предлагает небольшой сценарий, позволяющий включать изображения в Ваш код. А для создаваемого интерфейса Qt Designer генерит читаемый код.
Цены
MFC SDK Вы получаете бесплатно при покупке Visual Studio.
UNIX-версия Qt свободна (доступна по лицензии GPL) для разработки свободного программного обеспечения. Также доступна некоммерческая версия для платформы Windows. Но для разработки коммерческого программного обеспечения Вы должны приобрести лицензию Qt. Лицензируется в отдельности каждая платформа (Unix, MacOS или Windows) и каждый разработчик. Лицензия приобретается раз и навсегда и включает в себя один год технической поддержки. Выплаты за распространение runtime-библиотеки отсутствуют. Цена составляет 1550 $, что очень дорого для небольшой компании. При покупке более одной лицензии предлагаются скидки. Заметьте, что цена меньше затрат двухнедельного содержания разработчика. Инвестиции стоят того.
Распространение
При распространении приложения, созданного с помощью MFC, Вы можете положиться на MFC-библиотеку, поставляемую вместе с Windows. Но это небезопасно. Под одним и тем же названием, MFC42.DLL, могут скрываться три различные версии этой библиотеки. Поэтому нужно убедиться, что пользователь имеет необходимую версию библиотеки, и, в противном случае, ее обновить. Обновление библиотеки MFC может повлиять на работу многих приложений. Это недостаточно удобно. Что, если после установки моей программы компьютер перестанет работать?
Названия библиотек Qt недвусмысленны ( qt-mt303.dll ), поэтому нет никакого риска, что установка библиотеки qt-mt303.dll повлияет на работу приложения, использующего, скажем, qt-203.dll. Также отсутствует проблема обновления системы в целом.
Другие преимущества Qt
Инструментарий Qt обладает не только функциональностью MFC, доступной более простым путем, но и многими возможностями, не имеющими аналогов в MFC:
- Элементы управления: Qt – это графический инструментарий, предлагающий богатый набор графических элементов управления: текстовые поля, раскрывающиеся списки, кнопки-переключатели, … Некоторые из них очень сложны. Например, список (listview) обладает возможностью многоколоночного представления с пиктограммами и кнопками-переключателями.
- XML: Qt предлагает классы для работы с документами XML ( используется Sax2 или Dom ). Это простая, надежная и функциональная реализация.
- Регулярные выражения: Qt предлагает полную поддержку для Perl-совместимых регулярных выражений. Это дальнейшее развитие простых метасимволов ‘?’ и ‘*’. Регулярные выражения – это очень мощный инструмент для анализа данных, поиска образцов внутри документа, создания фильтров, определения маски для полей ввода.
- Много-платформенность: Qt 3 доступна для Windows (любой версии), Unix (любой версии), MacOS X и встроенных устройств. Для запуска Вашего приложения на другой платформе Вам достаточно его лишь перекомпилировать. Если компилятор не имеет каких-либо особенностей или ограничений, Вам не нужно прикасаться к Вашему коду.
- Классы шаблонов: Qt предлагает полезные классы для манипулирования списками, файлами, каталогами, строками, потоками, … Они более функциональны и удобны, чем аналогичные из STL и MFC.
- Управление памятью: Qt обладает различными средствами, упрощающими управление памятью. Объекты Qt автоматически уничтожаются при уничтожении их родителя. Многие классы обладают возможностью неявного совместного использования, которая освобождает Вас от забот по уничтожению и копированию этих объектов.
- Сетевой API: Qt предоставляет классы, облегчающие написание сетевых приложений: сокеты, DNS, FTP, HTTP, …
- API баз данных: Qt предоставляет классы для работы с базами данных: специальные виджеты, подключения к базам данных, SQL-запросы, …
- OpenGL API: Qt предлагает классы для работы с библиотекой OpenGL.
- Двухмерная графика ( Canvas ) : Qt предлагает классы, оптимизированные для обработки быстро перемещающихся двухмерных объектов, известных как спрайты.
- Стили: Можно полностью настроить внешний вид элементов управления. Qt может эмулировать стили всех известных инструментариев: Motiff, MFC, NextStep, …
Что такое CodeJock ?
Многочисленные недостатки MFC явились причиной возникновения различных MFC-оберток, которые помогают легко создавать приложения. Мы использовали библиотеку CodeJock. Как CodeJock + MFC сравнится с Qt ?
- CodeJock – это обертка для библиотеки MFC, которая, в свою очередь, является оберткой для API Windows. Добавление большего количества оберток для скрытия проблем обычно не является хорошим решением. Все вышеперечисленные проблемы все еще существуют ( ресурсы, шаблоны, сообщения, уникод, интернационализация, … )
- Классы, предлагаемые библиотекой CodeJock, позволяют более легко использовать MFC ( за счет дополнительных возможностей, упрощенных методов или их большего количества ). Например, с ее помощью можно создать элемент управления Multi-Tab View, в то время как с MFC это принципиально невозможно. Однако библиотека весьма ограничена. Она предлагает чуть больше классов, чем MFC, а не полный набор. CodeJock больше напоминает набор исправлений, а не обертку.
- Качество библиотеки оставляет желать лучшего. Много ошибок не исправлено, добавлены новые. На протяжении первых 6 месяцев 2002 года было выпущено 3 версии ( 1.9.2.1, 1.9.2.2, 1.9.3.0 ), в каждой из которых исправлено более 50 ошибок. Библиотека не отлажена и не протестирована. Это не инструмент профессионального качества. Ее пользователи – это альфа-тестеры. Заметьте также, что API библиотеки меняется от версии к версии, поэтому Вы должны будете изменять свой код. Это признаки плохого дизайна.
- Чтение кода ( к сожалению, неизбежное для CodeJock, учитывая ее временами странное поведение ) раскрывает ужасную картину: методы более чем на 500 строк, избыточный код, множество переходов в середину из ниоткуда, отсутствие комментариев, различные уловки. Многочисленные методы классов объявлены общедоступными, тогда как должны быть защищенными и т.д.
- Документация бедна, а в некоторых местах и вовсе пуста ( методы присутствуют без каких-либо объяснений ). Похоже, что на протяжении последних выпусков она не обновлялась.
- CodeJock не обладает возможностями, которые нельзя было бы найти в Qt. Исключением является шестнадцатеричный редактор, который к сожалению имеет множество ошибок, и который Вы легко можете сделать в Qt ( примеры уже существуют ).
У Qt отличное качество кода. Библиотека надежна и устойчива. С целью ее значительного улучшения на протяжении последних 6 лет исходная и двоичная совместимости были лишь дважды нарушены. И только один раз ( при переходе от Qt1 к Qt2 ) требовалась существенная переделка кода.
Вывод
Вывод, следующий из полученного нами опыта, очевиден. Qt намного лучше MFC. Вы создаете программы лучше и быстрее.
Некоторые люди жаловались, что эта статья снисходительна к Qt и не раскрывает всех возможностей MFC. Просто это наш опыт: мы имели множество проблем с MFC, и почти не имели их с Qt. Разработка с помощью Qt всегда была простой, документированной и эффективной. Если MFC и имеет преимущества, то мы их не нашли, за исключением бесплатной поставки вместе с Visual Studio.
Мы доступны для обратной связи: пишите нам для предложений, усовершенствований, замечаний и флейма !
Я хотел бы включить замечания людей, использовавших MFC и Qt. Если Вы к ним относитесь, напишите мне, пожалуйста.
Источник: Сравнение MFC и Qt
Оригинал: Impressions on MFC vs Qt Programming
Свежие комментарии