Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ 18 ] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

теперь он "раздельно" компилируемый?

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

Проблема первая: открытый исходный текст

Первая проблема остается нерешенной: исходный текст определений должен быть открыт.

Ничто в стандарте С++ не говорит (и ни из каких положений стандарта не вытекает), что вы можете не предоставлять пользователю полный исходный текст шаблона g только потому, что вы использовали ключевое слово export. На самом деле, в единственной реализации поддержки export компилятор требует, чтобы пользователю предоставлялось полное определение шаблона - т.е. полный исходный текст. Одна из причин этого заключается в том, что компилятору С++ требуется полный контекст определения экспортируемого шаблона при его инстанцировании. Для лучшего понимания проанализируем, что, согласно стандарту С++, происходит при инстанцировании шаблона:

"[Зависимые] имена не связаны и их поиск выполняется в точке инстанцирования шаблона как в контексте определения шаблона, так и в контексте точки инстанцирования". - [С++03, §14.6.2]

Зависимое имя - это имя, которое зависит от типа аргумента шаблона; они используются в большинстве полезных шаблонов. В точке инстанцирования или использования шаблона поиск зависимых имен осуществляется в двух местах. Их поиск должен выполняться в контексте инстанцирования, что достаточно просто, поскольку компилятор в этот момент обрабатывает именно эту часть программы. И о поиск этих имен должен также осуществляться в контексте определения шаблона, а это уже сложнее, поскольку для этого требуется не только знание полного определения шаблона, но и контекст этого определения в содержащем его файле, включая сигнатуры используемых функций и т.п. вещи, необходимые для выполнения разрешения перегрузки и других важных действий.

Рассмотрим пример 9-4(6) с точки зрения компилятора. Ваша библиотека содержит экспортируемый шаблон функции g с тщательно спрятанным вне заголовочного файла определением. Допустим, все хорошо, библиотека продана. Годом позже, в один прекрасный день библиотека используется в некотором пользовательском модуле h. срр, где требуется инстанцирование g<custType> для некоторого типа custiype, созданного этим утром... и что следует делать компилятору, чтобы сгенерировать объектный код? Компилятор должен, кроме прочего, выполнить просмотр определения g в вашем файле реализации. Как видите, экспорт не устраняет зависимости от определения шаблона, а всего лишь скрывает их.

Экспортированные шаблоны не являются "раздельно компилируемыми" в том смысле, который мы вкладываем в это понятие при работе с обычными функциями. В общем случае экспортируемые шаблоны не могут быть разделыю компилируемы в объектный код до их реального использования. Более того, до достижения точки использования шаблона мы не знаем даже, с какими типами аргументов будет инсганцироваи

При этом возникает обычный вопрос: а нельзя ли передавать зашифрованный исходный текст? Дело в том, что шифрование, при котором для дешифрования не требуется вмешательство пользователя (например, ввод пароля), легко взламывается. Некоторые компании пытались применять шифрование исходного кода, но быстро отказались от этой практики, поскольку реальная защита кода при этом не обеспечивается, зато очень сильно раздражает пользователей. Есть куда более хорошие методы защиты интеллектуальной собственности.



этот шаблон. Таким образом, экспортируемые шаблоны в лучшем случае "раздельно частично компилируемы" или "раздельно синтаксически анализируе-.мы". Определения шаблонов должны быть реально с ко м п и л и ро ван ы при каждом инстанцировании (здесь есть определенная схожесть с библиотеками Java или .NET, в которых из байт-кода или 1L .может быть получено достаточно много информации об исходном тексте).

> Рекомендация

Запомните, что ключевое слово export не подразумевает настоящей раздельной компиляции, как это происходит в случае обычных нешаблонных функций.

Проблема вторая: зависимости и время построения

Вторая проблема тоже остается нерешенной: зависимости оказываются скрытыми, но при этом они никуда не исчезают.

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

> Рекомендация

Запомните, что ключевое слово export только скрывает зависимости, но не устраняет их.

Да, вызывающая функция больше не зависит явно от внутренних деталей д, поскольку определение g больше не вносится в единицу трансляции при помощи директивы #include; можно сказать, что зависимости скрыты на уровне чтения исходного кода человеком.

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

Заметим, что компилятор может быть достаточно интеллектуальным для того, чтобы работать так же и в случае модели включения - т.е. не перестраивать все файлы, использующие данный шаблон, а ограничиться только необходимыми действиями для выполнения всех инстанцировании (если код организован так, как показано в примере 9-4(6), с тем лишь отличием, что удалено ключевое слово export, и в файл g.h добавлена директива #include "g.cpp". Идея заключается в том, что компилятор может полагаться на правило одного определения, а не навязывать его, т.е. компилятор может просто полагать, что все инстанцирования с одинаковыми аргументами обязаны быть идентичны, вместо того чтобы выполнять все необходимые инстанцирования и убеждаться в том, что они действительно идентичны.

Кроме того, вспомните, что многие шаблоны используют другие шаблоны, и таким образом, компилятор должен выполнять каскадную перекомпиляцию таких шаблонов



(и их единиц трансляции), и этот рекурсивный процесс продолжается до тех пор, пока не будут выполнены все каскадные инстанцирования (реакция нормального профамм иста в такой ситуации: "Какое счастье, что я не занимаюсь реализацией поддержки экспорта в компиляторе!").

Как видите, даже при использовании ключевого слова export внесение изменений в экспортируемый шаблон не позволяет ограничиться перекомпоновкой приложения. В отличие от истинной раздельной компиляции функций, где построение приложения при внесении изменений в одну функцию существенно быстрее, чем полная его перекомпиляция, при использовании экспорта шаблонов сказать заранее, будет ли построение приложения быстрее или медленнее полной его перекомпиляции, в общем случае невозможно.

Резюме

Итак, теперь вы понимаете, почему невозможно добиться истинной "раздельной компиляции" шаблонов так же, как это делалось для нешаблонных функций. Многие профаммисты считают, что экспорт означает возможность поставки библиотеки шаблонов пользователю без полного исходного текста, и/или ускорение построения приложения. Ничего подобного export не обещает. Опыт говорит, что должен поставляться весь исходный текст или его прямой эквивалент и что скорость построения приложения оказывается такой же или меньшей, и очень редко - большей, поскольку зависимости оказываются только замаскированы, но не устранены, так что компилятору в общем случае требуется выполнить ту же (если не большую) работу, что и в модели включения.

В следующей задаче мы увидим, почему export усложняет С++ и его использование, вплоть до фундаментальных изменений смысла других конструкций языка, порой столь неожиданных, что их просто трудно предвидеть. В этой же задаче будут приведены некоторые советы по эффективному использованию возможностей экспорта (на тот случай, если вам когда-то доведется столкнуться с компилятором, поддерживающим данную возможность).



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ 18 ] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85