Подтвердить что ты не робот

Как сократить время компиляции с помощью шаблонов С++

Я пытаюсь изменить часть своего приложения на С++, используя старый массив типов C, в шаблонный контейнер класса С++. Подробнее см. этот вопрос. Хотя решение работает очень хорошо, каждое незначительное изменение, которое я делаю на шаблонный код, вызывает очень большую рекомпиляцию, и, следовательно, резко замедляет время сборки. Есть ли способ получить шаблон кода из заголовка и обратно в файл cpp, так что незначительные изменения в реализации не вызывают серьезных перестроек?

4b9b3361

Ответ 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

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