Как сократить время компиляции с помощью шаблонов C ++ - PullRequest
33 голосов
/ 13 мая 2010

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

Ответы [ 5 ]

23 голосов
/ 13 мая 2010

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

  • Ключевое слово экспорта теоретически может помочь, но оно плохо поддерживается и официально удалено в C ++ 11.
  • Явноесоздание шаблона (см. здесь или здесь ) - самый простой подход, если вы можете заранее предсказать, какие экземпляры вам понадобятся (и если вы не против сохранить этот список).
  • Внешние шаблоны , которые уже поддерживаются несколькими компиляторами в качестве расширений.Насколько я понимаю, внешние шаблоны не обязательно позволяют перемещать определения шаблонов из файла заголовка, но они ускоряют компиляцию и компоновку (за счет уменьшения количества случаев, когда код шаблона должен создаваться и связываться).
  • В зависимости от дизайна шаблона, вы можете перенести большую часть его сложности в файл .cpp.Стандартным примером является класс шаблонного вектора безопасного типа, который просто переносит небезопасный вектор типа void*;вся сложность заключается в векторе void*, который находится в файле .cpp.Скотт Мейерс приводит более подробный пример в Effective C ++ (пункт 42, «Разумное использование частного наследования», во 2-м издании).
17 голосов
/ 13 мая 2010

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

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

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

6 голосов
/ 13 мая 2010
  • Вы можете получить компилятор, который поддерживает ключевое слово export , но вряд ли это продлится долго.

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

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

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

5 голосов
/ 13 мая 2010

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

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

Давайте рассмотрим пример:

// "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, то at явно не будет иметь смысла, поскольку не может связываться с внутренностями класса, уменьшая таким образом количество подозреваемые.

Это рекомендуется многими авторами, такими как Herb Sutters в C ++ Стандарты кодирования :

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

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

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

3 голосов
/ 13 мая 2010

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

...