Я пытаюсь изменить часть своего приложения на С++, используя старый массив типов C, в шаблонный контейнер класса С++. Подробнее см. этот вопрос. Хотя решение работает очень хорошо, каждое незначительное изменение, которое я делаю на шаблонный код, вызывает очень большую рекомпиляцию, и, следовательно, резко замедляет время сборки. Есть ли способ получить шаблон кода из заголовка и обратно в файл cpp, так что незначительные изменения в реализации не вызывают серьезных перестроек?
Как сократить время компиляции с помощью шаблонов С++
Ответ 1
Я думаю, что применяются общие правила. Попытайтесь уменьшить сцепление между частями кода. Разбивайте слишком большие заголовки шаблонов на более мелкие группы функций, используемых вместе, поэтому все это не обязательно должно быть включено в каждый исходный файл.
Кроме того, попробуйте быстро получить заголовки в стабильное состояние, возможно, протестировав их против меньшей тестовой программы, поэтому им не потребуется изменять (слишком много) при интеграции в более крупную программу.
(Как и при любой оптимизации, оптимизация скорости компилятора при работе с шаблонами может быть менее целесообразной, чем поиск "алгоритмической" оптимизации, которая в первую очередь снижает нагрузку на рабочую нагрузку.)
Ответ 2
Несколько подходов:
- ключевое слово экспорта теоретически может помочь, но оно плохо поддерживается и официально удалено на С++ 11.
- Явная реализация шаблона (см. здесь или здесь) - самый простой подход, если вы можете заранее предсказать, какие экземпляры вам понадобятся (и если вы не возражаете против сохранения этого списка).
- Внешние шаблоны, которые уже поддерживаются несколькими компиляторами в качестве расширений. Я понимаю, что шаблоны extern не обязательно позволяют вам перемещать определения шаблонов из файла заголовка, но они делают компиляцию и компоновку быстрее (за счет сокращения количества экземпляров и привязки кода шаблона).
- В зависимости от дизайна вашего шаблона вы можете перенести большую часть своей сложности в файл .cpp. Стандартный пример - это тип шаблона векторного типа, который просто переносит небезопасный вектор типа
void*
; вся сложность идет в вектореvoid*
, который находится в файле .cpp. Скотт Мейерс дает более подробный пример в Эффективном С++ (пункт 42, "Использовать частное наследование разумно", во втором издании).
Ответ 3
Прежде всего, для полноты я расскажу о простом решении: при необходимости используйте только шаблонный код и основывайте его на не-шаблоном (с реализацией в собственном исходном файле).
Однако я подозреваю, что реальная проблема заключается в том, что вы используете универсальное программирование, так как вы бы использовали типичное OO-программирование и заканчивали раздутым классом.
Возьмем пример:
// "bigArray/bigArray.hpp"
template <class T, class Allocator>
class BigArray
{
public:
size_t size() const;
T& operator[](size_t index);
T const& operator[](size_t index) const;
T& at(size_t index);
T const& at(size_t index);
private:
// impl
};
Это вас шокирует? Возможно нет. В конце концов, это похоже на минимализм. Дело в том, что нет. Методы at
могут быть учтены без потери общности:
// "bigArray/at.hpp"
template <class Container>
typename Container::reference_type at(Container& container,
typename Container::size_type index)
{
if (index >= container.size()) throw std::out_of_range();
return container[index];
}
template <class Container>
typename Container::const_reference_type at(Container const& container,
typename Container::size_type index)
{
if (index >= container.size()) throw std::out_of_range();
return container[index];
}
Хорошо, это немного меняет вызов:
// From
myArray.at(i).method();
// To
at(myArray,i).method();
Однако, благодаря поиску Koenig, вы можете назвать их неквалифицированными, пока вы помещаете их в одно и то же пространство имен, так что это просто вопрос привычки.
Пример изобретателен, но общая точка стоит. Обратите внимание, что из-за своей универсальности at.hpp
никогда не приходилось включать bigArray.hpp
и все равно создавал бы такой же жесткий код, как если бы это был метод-член, просто мы можем вызвать его в других контейнерах, если хотим.
И теперь пользователю BigArray
не нужно включать at.hpp
, если она его не использует... таким образом уменьшая ее зависимости и не подвергаясь воздействию, если вы меняете код в этом файле: например alter std::out_of_range
, чтобы указать имя файла и номер строки, адрес контейнера, его размер и индекс, который мы пытались получить.
Другое (не столь очевидное) преимущество заключается в том, что если ограничение целостности BigArray
нарушается, то at
явно не имеет причин, так как он не может взаимодействовать с внутренними элементами класса, тем самым уменьшая количество подозреваемых.
Это рекомендуется многими авторами, такими как Herb Sutters, в Стандарты кодирования С++:
Пункт 44: Предпочитает записывать функции non-members nonfriend.
и был широко использован в Boost... Но вам нужно изменить свои привычки кодирования!
Тогда, конечно, вам нужно включить только то, от чего вы зависите, должны быть статические анализаторы кода на С++, которые содержат включенные, но неиспользуемые файлы заголовков, которые могут помочь в этом.
Ответ 4
-
Вы можете получить компилятор, который поддерживает export ключевое слово, но это вряд ли будет продолжаться.
-
Вы можете использовать явное создание экземпляра, но, к сожалению, это требует, чтобы вы прогнозировали типы шаблонов, которые вы будете использовать раньше времени.
-
Если вы можете отчислить шаблонные типы из вашего алгоритма, вы можете поместить его в свой собственный .cc файл.
-
Я бы не предложил это, если это не серьезная проблема, но: вы можете предоставить интерфейс контейнера шаблонов, который реализован с помощью вызовов реализации
void*
, которые вы можете свободно изменять по своему усмотрению.
Ответ 5
Вы можете определить базовый класс без шаблонов и переместить большую часть реализации там. Затем шаблонный массив будет определять только прокси-методы, которые используют базовый класс для всего.