Есть ли какие-то общие результаты/сравнительные исследования этих парадигм?
из того, что я видел, много примеров доказательств можно найти в статьях и публикациях. ваши любимые книги по C++ должны содержать несколько демонстраций; если у вас нет такого ресурса, вы можете прочитать Modern C++ Design: Generic Programming and Design Patterns Applied — A. Alexandrescu. хотя на ум не приходит нет конкретного ресурса, который прямо отвечает на ваш вопрос. кроме того, результат зависит от реализации и компилятора — даже настройки компилятора могут сильно повлиять на результат такого теста. (отвечая на каждый из ваших вопросов, хотя это не считается ответом на этот конкретный вопрос).
Ускорение существенное?
короткий ответ: это зависит.
в вашем примере компилятор может фактически использовать статическую диспетчеризацию или даже встраивать вызовы виртуальных функций (достаточно информации для компилятора). теперь я собираюсь переместить ответы от тривиального примера (в частности, ОП) к более крупным и сложным программам.
расширение на «это зависит»: да, скорость может варьироваться от неизмеримой до огромной. вы должны (и, вероятно, уже) понимать, что компилятору может быть предоставлено невероятное количество информации при компиляции через дженерики. затем он может использовать эту информацию для более точной оптимизации вашей программы. хорошим примером этого является использование std::array
против std::vector
. вектор добавляет гибкости во время выполнения, но стоимость может быть весьма значительной. вектор должен реализовать больше для изменения размера, необходимость динамического распределения может быть дорогостоящей. есть и другие отличия: резервное выделение массива не изменится (++оптимизация), количество элементов фиксировано (++оптимизация) и опять же - во многих случаях нет необходимости вызывать new.
теперь вы можете подумать, что этот пример значительно отличается от исходного вопроса. во многом это не так уж и отличается: компилятор узнает о вашей программе все больше и больше по мере увеличения ее сложности. эта информация может удалить несколько частей вашей программы (мертвый код), и, используя std::array
в качестве примера, информации, предоставляемой типом, достаточно, чтобы компилятор мог легко сказать: «о, я вижу, что размер этого массива составляет семь элементов, я разверну цикл соответственно», и у вас будет меньше инструкций и устранены неверные предсказания. это еще не все, но в случае массива/вектора я видел, как размер исполняемого файла оптимизированных программ уменьшается до 20% при преобразовании из vector
в интерфейс, похожий на array
. также код может выполняться в несколько раз быстрее. на самом деле, некоторые выражения могут быть полностью вычислены при компиляции.
динамическая диспетчеризация по-прежнему имеет свои достоинства, и использование динамической диспетчеризации также может повысить скорость вашей программы при правильном использовании - то, что вам действительно нужно изучить, сводится к решению, когда отдать предпочтение одному над другим. Подобно тому, как огромная функция со многими переменными не может быть очень эффективно оптимизирована (результат всего этого расширения шаблона в реальной программе), вызов виртуальной функции на самом деле может быть более быстрым и чистым подходом во многих ситуациях. как таковые, это две отдельные функции, вам потребуется некоторая практика, чтобы определить, что правильно (и многие программисты не тратят время, чтобы изучить это достаточно хорошо).
в заключение их следует рассматривать как отдельные функции, применимые к различным сценариям. они должны были (имхо) иметь гораздо меньше практического совпадения, чем на самом деле в реальном мире.
А время компиляции?
с шаблонами время компиляции и ссылки во время разработки может быть довольно большим. каждый раз, когда изменяется заголовок/шаблон, вам потребуется компиляция всех зависимостей - это часто может быть значительным преимуществом в пользу динамической диспетчеризации. вы, конечно, можете уменьшить это, если будете планировать заранее и строить соответствующим образом - понять, как гораздо сложнее освоить шаблоны. с помощью шаблонов вы не только увеличиваете частоту больших сборок, но часто увеличиваете время и сложность больших сборок. (дальше примечания)
Каковы последствия дизайна для интерфейсов в более крупных системах (я в основном использовал A для своих межмодульных интерфейсов и до сих пор не занимался действительно большими проектами)?
это действительно зависит от ожиданий вашей программы. я пишу на virtual
меньше с каждым годом (и многие другие тоже). среди других подходов все большее распространение получают шаблоны. честно говоря, я не понимаю, почему B
"анархистский". для меня A
немного анахроничен, так как есть много подходящих альтернатив. в конечном счете, это выбор дизайна, который может потребовать много внимания для хорошей архитектуры больших систем. хорошая система будет использовать здоровую комбинацию возможностей языка. история доказывает, что ни одна функция в этом обсуждении не является необходимой для написания нетривиальной программы, но все функции были добавлены, потому что кто-то увидел лучшие альтернативы для некоторых конкретных применений. вы также должны ожидать, что лямбда-выражения заменят виртуальные более чем в 50% случаев их текущего использования в некоторых (не всех) командах/кодовых базах.
Обобщения:
- шаблоны могут выполняться значительно быстрее, если их правильно использовать.
- любой из них может создавать исполняемые файлы большего размера. при правильном использовании и важном размере исполняемого файла автор будет использовать несколько подходов для уменьшения размера исполняемого файла, обеспечивая при этом удобные интерфейсы.
- шаблоны могут стать очень сложными. научиться сканировать и интерпретировать сообщения об ошибках требует времени.
- шаблоны отправляют несколько ошибок в область компиляции. лично я предпочитаю ошибку компиляции ошибке времени выполнения.
- сокращение времени компиляции с помощью виртуальных файлов часто бывает простым (виртуальные файлы относятся к .cpp). если ваша программа огромна, то огромные системы шаблонов, которые часто меняются, могут быстро увеличить время перестройки и подсчеты, потому что будет много межмодульной видимости и зависимости.
- отложенное и/или выборочное создание экземпляров с меньшим количеством скомпилированных файлов можно использовать для сокращения времени компиляции.
- в более крупных системах вам придется гораздо более внимательно относиться к форсированию нетривиальной перекомпиляции для вашей команды/клиента. использование виртуалов — один способ минимизировать это. кроме того, более высокий процент ваших методов будет определен в файле cpp. альтернативой, конечно же, является то, что вы можете скрыть большую часть реализации или предоставить клиенту более выразительные способы использования ваших интерфейсов.
- в более крупных системах шаблоны, лямбда-выражения/функторы и т. д. действительно могут использоваться для значительного уменьшения связанности и зависимостей.
- виртуальные системы увеличивают зависимость, часто становятся сложными в обслуживании, раздувают интерфейсы и становятся структурно неуклюжими зверями. библиотеки, ориентированные на шаблоны, имеют тенденцию инвертировать этот приоритет.
- все подходы могут быть использованы по неправильным причинам.
Итог большая, хорошо спроектированная современная система будет эффективно и одновременно использовать множество парадигм. если вы используете виртуальные машины большую часть времени в настоящее время, вы (imo) делаете это неправильно, особенно если это все еще подход после того, как у вас было время освоить С++ 11. если скорость, производительность и/или параллелизм также являются существенными проблемами, то шаблоны и лямбда-выражения заслуживают того, чтобы стать вашими более близкими друзьями.
19.09.2011