Wiki

Почему в Qt для сигналов и слотов не используются шаблоны

Источник: Why doesn't Qt use templates for signals and slots? Перевод: Andi Peredri Простым ответом на этот вопрос является тот факт, что на момент проектирования Qt не было возможности полноценно использовать механизм шаблонов при разработке кросс-платформенных приложений без надлежащей их поддержки со стороны различных компиляторов. Даже сегодня многие распространенные компиляторы C++ имеют проблемы со сложными шаблонами. Например, не может быть гарантирована переносимость специализированных шаблонов классов, что является весьма важным при решении сложных задач. Поэтому использование шаблонов в Qt достаточно консервативно. Помните, что Qt является кросс-платформенным инструментарием, поэтому прогресс на платформе Linux/g++ не означает улучшение ситуации повсеместно. Со временем поддержка шаблонов в этих компиляторах улучшится. Но даже если всем нашим пользователям станут доступны стопроцентно совместимые со стандартом C++ компиляторы с отличной поддержкой шаблонов, мы не откажемся от используемого в нашем мета-объектном компиляторе подхода на основе строк. Вот пять причин, почему:

1. Вопросы синтаксиса

Синтаксис - это не только дело вкуса: синтаксис, используемый нами для выражения наших алгоритмов, оказывает существенное влияние на читаемость и сопровождаемость нашего кода. Синтаксис, применяемый в Qt для сигналов и слотов, был успешно опробован на практике. Он интуитивен, прост в использовании, легко читаем и способствует быстрому усвоению начинающими разработчиками концепции сигналов и слотов, несмотря на их довольно абстрактную и общую природу. Кроме этого, используемая декларация сигналов в определении классов гарантирует им статус защищенных методов. Это помогает программистам изначально правильно спланировать классы, не задумываясь об их дизайне.

2. Пре-компиляторы - это хорошо

Мета-объектный компилятор Qt moc позволяет легко расширять возможности компилируемого языка. Это достигается генерацией дополнительного C++ кода, который может быть скомпилирован любым стандартным компилятором C++. Компилятор moc читает исходные файлы C++. Если он встречает макрос Q_OBJECT хотя бы в одной из деклараций класса, он создает другой исходный файл C++, содержащий мета-объектный код для этих классов. Сгенерированный таким образом файл должен быть откомпилирован вместе с файлом реализации класса ( или может быть включен в него с помощью директивы #include ). Обычно moc вызывается не вручную, а автоматически системой сборки, поэтому со стороны программиста не требуется дополнительных усилий. Также имеются другие пре-компиляторы. Например, rpc и idl облегчают реализацию сетевого и межпроцессного взаимодействия программ и объектов. Альтернативой пре-компиляторам являются модифицированные компиляторы, собственные языки и графические средства программирования с диалогами и мастерами, генерирующими невразумительный код. Вместо навязывания нашим клиентам собственного компилятора C++ или специализированной интегрированной среды разработки мы оставляем за ними право использовать любой предпочитаемый инструмент. Мы не принуждаем разработчиков добавлять сгенерированные файлы в репозитарий исходного кода, а рекомендуем им использовать наши инструменты в их системе сборки. Такой подход прозрачен, надежен и больше соответствует духу UNIX.

3. Гибкость превыше всего

C++ - это стандартизированный, мощный и сложный язык общего назначения, получивший самое широкое распространение при разработке программного обеспечения: от целых операционных систем, серверов баз данных и профессиональных графических пакетов - до рядовых настольных программ. Одной из составляющих успеха C++ является его продуманный дизайн, ориентированный на максимальную производительность, минимальный расход памяти и сохранение совместимости с ANSI-C. При всех этих преимуществах имеются также некоторые недостатки. Статическая объектная модель C++ полностью уступает динамической системе сообщений Objective C при разработке компонентов пользовательского графического интерфейса. То, что является преимуществом при разработке серверов баз данных и операционных систем, не обязательно таковым остается при проектировании GUI. С помощью moc мы обращаем этот недостаток в преимущество и обеспечиваем гибкость, столь необходимую для эффективной разработки пользовательского графического интерфейса. Наше решение предоставляет гораздо больше возможностей, чем шаблоны. Например, мы можем наделить объекты свойствами и перегрузить сигналы и слоты, что есть естественно для языка программирования, в котором перегрузка является ключевой особенностью. Наши сигналы не добавляют ни одного байта к размеру экземпляра класса, поэтому мы можем добавлять новые сигналы без потери бинарной совместимости. Механизм сигналов и слотов, в отличие от шаблонов, не приводит к чрезмерной генерации кода, благодаря чему его размер остается меньшим. Добавление новой связи приводит лишь к вызову простейшей функции, а не к генерации сложной шаблонной функции. Другим преимуществом является возможность манипулирования сигналами и слотами объекта во время выполнения. Используя вызов по имени, мы можем безопасно создавать связи без необходимости точного знания типов связываемых объектов, чего нельзя достичь с помощью шаблонов. Такой вид доступа к объектам во время выполнения открывает новые возможности, например, создание GUI-интерфейса и необходимых связей из XML UI-файлов.

4. Производительность вызовов не критична

Механизм сигналов и слотов Qt не обеспечивает той производительности, которую обеспечивают шаблоны. Например, затраты на генерацию одного сигнала шаблонными средствами приблизительно эквивалентны четырем обычным вызовам функций, а для решения этой задачи средствами Qt необходимы затраты, эквивалентные десяти вызовам функций. И в этом нет ничего удивительного, потому что механизм сигналов и слотов производит дополнительный поиск и проверку объектов, а также обеспечивает возможность использования сценариев. Он не прибегает к чрезмерному встраиванию и разворачиванию кода и обеспечивает гарантированную безопасность во время выполнения. Итераторы классов Qt безопаснее итераторов более быстрых стандартных шаблонных классов. Даже в процессе отправки сигнала нескольким объектам-адресатам последние могут быть удалены без риска аварийного завершения программы. Без таких гарантий ваше приложение могло бы иногда аварийно завершаться с весьма сложными для отладки ошибками чтения-записи памяти. И все-таки, можно ли шаблонными средствами добиться значительного улучшения производительности приложений, использующих сигналы и слоты? Несмотря на то, что при использовании Qt возрастают накладные расходы, связанные с отсылкой сигнала, они составляют лишь малую часть от суммарных затрат на вызов слота. Обычно замеры производительности системы сигналов и слотов Qt производятся с пустыми слотами. Однако, если в слот добавить что-нибудь полезное, например, несколько простых операций со строками, то накладные расходы, связанные с отсылкой сигнала, станут ничтожными. Несмотря на оптимизацию использования в Qt операторов new и delete, такая операция, как вставка-удаление элементов классов-контейнеров, значительно превзойдет по ресурсоемкости процедуру отправки сигнала. С другой стороны, если вы генерируете сигнал из высокопроизводительного и критичного ко времени исполнения цикла и определили, что он является "самым узким местом" в программе, подумайте об использовании в этом случае вместо механизма сигналов и слотов цикла ожидания. В противном случае, постарайтесь придерживаться при связывании сигналов и слотов соотношения 1:1. Например, для объекта, принимающего данные из сети, вполне подойдет использование сигнала при получении запрошенных данных. Но если вам необходимо осуществить побайтную передачу, используйте цикл ожидания.

5. Без ограничений

Наряду с поддержкой механизма сигналов и слотов moc может быть также использован для реализации других возможностей, не обеспечиваемых шаблонами. В качестве примера могут послужить генерируемые функции контекстного перевода tr() и расширенная система доступа к свойствам объектов с поддержкой RTTI ( Runtime Type Information ). Одна только система свойств предоставляет огромную выгоду: такой мощный инструмент создания компонентов пользовательского графического интерфейса, как Qt Designer, было бы намного сложнее разработать без мощной системы доступа к свойствам объектов. Использование препроцессора moc предоставило нам гибкость Objective-C и Java Runtime Environment, отличную производительность и масштабируемость C++. Вот что сделало Qt тем удобным и гибким инструментом, который мы имеем сегодня.