|
Программирование >> Обобщенные обратные вызовы
Для обычной функции можно ответить легко , так как оба недостатка преодолеваются путем применения раздельной компиляции. пример 9-4Са): раздельная компиляция функции - Файл f.h, предоставляемый пользователю - namespace MyLib { void f( int ); - Файл f.cpp, может не быть предоставлен - namespace MyLib { void f( int ) { Изящный код - результат многих лет работы; использует вспомогательные классы и функции; компилируется отдельно } Нет ничего неожиданного в том, что данный подход решает обе проблемы, как минимум, для функций. (Та же идея может быть применена и для целых классов - о применении идиомы Pimpl вы можете прочесть в книге [SutterOO].) Исходный текст определений скрыт. Мы можем поставлять пользователям исходный текст определений, если хотим этого, но мы не обязаны это делать. Заметим, что многие популярные библиотеки поставляются с исходным текстом (возможно, за отдельную плату), причем так поступают даже производители, которые строго следят за своими имушествснны.ми правами. Исходные тексты могут потребоваться пользователям, например, для отладочных целей или по каким-либо иным причинам. Отсутствие зависимостей исходного кода. Вызывающая функция больше не зависит от деталей внутреннего устройства функции f, так что при ее изменении не требуется полная перекомпиляция, достаточно перекомпоновки, что зачастую на порядок (а то и более) быстрее. Кроме того (что, правда, обычно не столь сильно влияет на время сборки приложения), вызывающая f функция больше не зависит от типов, использованных только в теле функции f. Все это хорошо известно и хорошо работает для функций. Программисты знакомы с этим методом еще со времен С, и даже еще раньше, т.е. уже много-много лет... К настоящему же вопросу мы только подбираемся...Итак, а как обстоят дела б) для шаблонов? Идея, лежащая в основе экспортирования, заключается в том, чтобы получить нечто аналогичное, но для шаблонов. Некоторые могут наивно ожидать, что приведенный ниже код обладает теми же достоинствами, что и код из примера 9-4(а). Это абсолютно неоправданно. Но не расстраивайтесь, многие знатоки С++- заблуждаются точно так же. Пример 9-4(6): экспорт шаблона - Файл g.h, предоставляемый пользователю - namespace MyLib { export tempiate<typename т> void g( T& ); - Файл g.cpp, ?? предоставляемый пользователю?? - namespace MyLib { tempiate<typename T> void g( T& ) { изящный код - результат многих лет работы; использует вспомогательные классы и функции. теперь он раздельно компилируемый? Для многих оказывается неожиданным, что для шаблонов такой код не решает ни одну из указанных ранее проблем. Он может только несколько улучшить ситуацию с одной из них. Давайте еще раз рассмотрим эти проблемы. Проблема первая: открытый исходный текст Первая проблема остается нерешенной: исходный текст определений должен быть открыт. Ничто в стандарте С++ не говорит (и ни из каких положений стандарта не вытекает), что вы можете не предоставлять пользователю полный исходный текст шаблона g только потому, что вы использовали ключевое слово export. На самом деле, в единственной реализации поддержки export компилятор требует, чтобы пользователю предоставлялось полное определение шаблона - т.е. полный исходный текст. Одна из причин этого заключается в том, что компилятору С++ требуется полный контекст определения экспортируемого шаблона при его и и ста i щи роваи и и. Для лучшего понимания проанализируем, что, согласно стандарту С++, происходит при иистаициро-вании шаблона: [Зависимые] имена не связаны и их поиск выполняется в точке инстанцирования шаблона как в контексте определения шаблона, так и в контексте точки инстанцирования . - [С++03, §14.6.2] Зависимое имя - это имя, которое зависит от типа аргумента шаблона; они используются в большинстве полезных шаблонов. В точке инстанцирования или использования шаблона поиск зависимых имен осуществляется в двух местах. Их поиск должен выполняться в контексте инстанцирования, что достаточно просто, поскольку компилятор в этот момент обрабатывает именно эту часть программы. Но поиск этих имен должен также осуществляться в контексте определения шаблона, а это уже сложнее, поскольку для этого требуется не только знание полного определения шаблона, но и контекст этого определения в содержащем его файле, включая сигнатуры используемых функций и т.п. вещи, необходимые для выполнения разрешения перегрузки и других важных действий. Рассмотрим пример 9-4(6) с точки зрения компилятора. Ваша библиотека содержит экспортируемый шаблон функции g с тщательно спрятанным вне заголовочного файла определением. Допустим, все хорошо, библиотека продана. Годом позже, в один прекрасный день библиотека используется в некотором пользовательском модуле h. срр, где требуется инстанцирование g<custType> для некоторого типа custType, созданного этим утром... и что следует делать компилятору, чтобы сгенерировать объектный код? Компилятор должен, кроме прочего, выполнить просмотр определения g в вашем файле реализации. Как видите, экспорт не устраняет зависимости от определения шаблона, а всего лишь скрывает их. Экспортированные шаблоны не являются раздельно компилируемыми в том смысле, который мы вкладываем в это понятие при работе с обычными функциями. В общем случае экспортируемые шаблоны не могут быть раздельно компилируемы в объектный код до их реального использования. Более того, до достижения точки использования шаблона мы не знаем даже, с какими типами аргументов будет инстанцироваи При этом возникает обычный вопрос: а нельзя ли передавать зашифрованный исходный текст? Дело в том, что шифрование, при котором для дешифрования не требуется вмешательство пользователя (например, ввод пароля), легко взламывается. Некоторые компании пытались применять шифрование исходного кода, но быстро отказались от этой практики, поскольку реальная защита кода при этом не обеспечивается, зато очень сильно раздражает пользователей. Есть куда более хорошие методы защиты интеллектуальной собственности. этот шаблон. Таким образом, экспортируемые шаблоны в лучшем случае раздельно частично компилируемы или раздельно синтаксически анализируе-.мы . Определения шаблонов должны быть реально с ко м п и л и ро ван ы при каждом инстанцировании (здесь есть определенная схожесть с библиотеками Java или .NET, в которых из байт-кода или 1L .может быть получено достаточно много информации об исходном тексте). > Рекомендация Запомните, что ключевое слово export не подразумевает настоящей раздельной компиляции, как это происходит в случае обычных нешаблонных функций. Проблема вторая: зависимости и время построения Вторая проблема тоже остается нерешенной: зависимости оказываются скрыты.ми, но при этом они никуда не исчезают. Всякий раз при изменениях в теле шаблона компилятор должен заново инстанци-ровать все применения данного шаблона. Во время этого процесса единицы трансляции, использующие шаблон д, должны обрабатываться вместе с внутренней реализацией д, т.е. вместе с определением g и типов, использующихся только в теле д. Код шаблона будет полностью скомпилирован позже, когда станет известен контекст каждого инстанцирования. > Рекомендация Запомните, что ключевое слово export только скрывает зависимости, но не устраняет их. Да, вызывающая функция больше не зависит явно от внутренних деталей д, поскольку определение g больше не вносится в единицу трансляции при помощи директивы #include; можно сказать, что зависимости скрыты на уровне чтения исходного кода человеком. Однако на этом проблемы не заканчиваются, поскольку мы говорим не о тех зависимостях, которые может прочесть программист, а о тех, которые должен скомпили-ровать компилятор, а эти зависимости никуда не исчезают. Да, ко.мпилятор может не перекомпилировать все единицы трансляции, в которых используется данный шаблон, но он должен, как минимум, выполнить компиляцию тех модулей, которые используют шаблон с комбинациями аргументов, для которых шаблон не был использован и для которых инстанцирование должно быть выполнено с нуля . Компилятор не в состоянии обеспечить истинную раздельную компиляцию и ограничиться только компоновкой имеющегося объектного кода. Заметим, что компилятор может быть достаточно интеллектуальным для того, чтобы работать так же и в случае модели включения - т.е. не перестраивать все файлы, использующие данный шаблон, а ограничиться только необходимыми действиями для выполнения всех инстанцировании (если код организован так, как показано в примере 9-4(6), с тем лишь отличием, что удалено ключевое слово export, и в файл g.h добавлена директива #include g.cpp . Идея заключается в том, что компилятор может полагаться на правило одного определения, а не навязывать его, т.е. компилятор может просто полагать, что все инстанцирования с одинаковыми аргументами обязаны быть идентичны, вместо того чтобы выполнять все необходимые инстанцирования и убеждаться в том, что они действительно идентичны. Кроме того, вспомните, что многие шаблоны используют другие шаблоны, и таким образом, компилятор должен выполнять каскадную перекомпиляцию таких шаблонов
|
© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |