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

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

34
задан Community 23 May 2017 в 10:30
поделиться

5 ответов

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

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

(Как и при любой оптимизации, при работе с шаблонами, возможно, будет менее целесообразно оптимизировать скорость компилятора, чем найти «алгоритмическую» оптимизацию, которая в первую очередь резко снижает рабочую нагрузку.)

{ {1}}
19
ответ дан 27 November 2019 в 16:50
поделиться

Несколько подходов:

  • Ключевое слово export теоретически могло помочь, но оно плохо поддерживалось и было официально удалено в C ++ 11.
  • Явное создание экземпляров шаблона (см. здесь или здесь ) - самый простой подход, если вы можете заранее предсказать, какие экземпляры вам понадобятся (а если вы этого не сделаете). не забывайте поддерживать этот список).
  • Шаблоны Extern , которые уже поддерживаются некоторыми компиляторами в качестве расширений. Насколько я понимаю, шаблоны extern не обязательно позволяют перемещать определения шаблонов из файла заголовка, но они ускоряют компиляцию и компоновку (за счет уменьшения количества экземпляров и связывания кода шаблона).
  • В зависимости от дизайна вашего шаблона вы можете перенести большую часть его сложности в файл .cpp. Стандартный пример - типобезопасный векторный шаблонный класс, который просто обертывает типизированный вектор void * ; Вся сложность заключается в векторе void * , который находится в файле .cpp. Скотт Мейерс приводит более подробный пример в Эффективный C ++ (пункт 42, «Разумно используйте частное наследование», во 2-м издании).
26
ответ дан 27 November 2019 в 16:50
поделиться

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

Однако я подозреваю, что реальная проблема заключается в том, что вы используете универсальное программирование, как если бы вы использовали типичное объектно-ориентированное программирование, и в итоге получили бы раздутый класс.

Возьмем пример:

// "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();

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

Пример надуманный, но общая мысль остается в силе. Обратите внимание, что из-за универсальности at.hpp никогда не приходилось включать bigArray.hpp , и он по-прежнему будет создавать такой жесткий код, как если бы это был метод-член, просто мы можем вызвать его на другие контейнеры, если захотим.

И теперь пользователю BigArray не нужно включать at.hpp , если он не использует его ... таким образом уменьшая ее зависимости и не влияя на изменение код в этом файле: например, вызов alter std :: out_of_range , чтобы указать имя файла и номер строки, адрес контейнера, его размер и индекс, к которому мы пытались получить доступ.

Другое (не столь очевидное) преимущество состоит в том, что если когда-либо ограничение целостности BigArray будет нарушено, то в , очевидно, не по причине, так как он не может вмешиваться во внутренние части класс, тем самым уменьшив количество подозреваемых.

Это рекомендуется многими авторами, такими как Херб Саттерс в Стандартах кодирования C ++ :

Правило 44: Предпочитайте писать функции, не являющиеся членами группы

, и широко использовалось в Boost ... Но вы должны изменить свои привычки кодирования!

Тогда, конечно, вам нужно включить только то, от чего вы действительно зависите, должны быть статические анализаторы кода C ++, которые сообщают о включенных, но неиспользуемых файлах заголовков, которые могут помочь в этом разобраться.

5
ответ дан 27 November 2019 в 16:50
поделиться

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

3
ответ дан 27 November 2019 в 16:50
поделиться
  • Вы можете получить компилятор, поддерживающий экспорт ключевое слово, но это вряд ли продлится долго.

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

  • Если вы можете исключить шаблонные типы из своего алгоритма, вы можете поместить его в отдельный файл .cc.

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

6
ответ дан 27 November 2019 в 16:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: