Я нахожусь в процессе изменяющейся части моего приложения C++ от использования более старого массива типа C к шаблонному контейнерному классу C++. Посмотрите этот вопрос для деталей. В то время как решение работает очень хорошо, каждое незначительное изменение, которое я вношу в шаблонный код, заставляет очень большой объем перекомпиляции происходить и следовательно решительно замедляет время изготовления. Есть ли какой-либо способ вытащить код шаблона из заголовка и назад в cpp файл, так, чтобы незначительные изменения реализации не вызывали главный, восстанавливает?
Я думаю, что общие правила применимы. Постарайтесь уменьшить взаимосвязь между частями кода. Разбейте слишком большие заголовки шаблонов на более мелкие группы функций, используемых вместе, чтобы все это не нужно было включать в каждый исходный файл.
Кроме того, постарайтесь быстро привести заголовки в стабильное состояние, возможно, протестируйте их с помощью небольшой тестовой программы, чтобы их не нужно было изменять (слишком много) при интеграции в более крупную программу.
(Как и при любой оптимизации, при работе с шаблонами, возможно, будет менее целесообразно оптимизировать скорость компилятора, чем найти «алгоритмическую» оптимизацию, которая в первую очередь резко снижает рабочую нагрузку.)
{ {1}}Несколько подходов:
void *
; Вся сложность заключается в векторе void *
, который находится в файле .cpp. Скотт Мейерс приводит более подробный пример в Эффективный C ++ (пункт 42, «Разумно используйте частное наследование», во 2-м издании). Прежде всего, для полноты, я расскажу о простом решении: используйте шаблонный код только при необходимости и основывайте его на не шаблонном коде (с реализация в собственном исходном файле).
Однако я подозреваю, что реальная проблема заключается в том, что вы используете универсальное программирование, как если бы вы использовали типичное объектно-ориентированное программирование, и в итоге получили бы раздутый класс.
Возьмем пример:
// "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 ++, которые сообщают о включенных, но неиспользуемых файлах заголовков, которые могут помочь в этом разобраться.
Вы можете определить базовый класс без шаблонов и перенести туда большую часть реализации. Тогда массив с шаблонами будет определять только прокси-методы, которые для всего используют базовый класс.
Вы можете получить компилятор, поддерживающий экспорт ключевое слово, но это вряд ли продлится долго.
Вы можете использовать явное создание экземпляра , но, к сожалению, это требует от вас заранее предсказать типы шаблонов, которые вы будете использовать.
Если вы можете исключить шаблонные типы из своего алгоритма, вы можете поместить его в отдельный файл .cc.
Я бы не предлагал этого, если это не серьезная проблема, но: Вы можете предоставить интерфейс контейнера шаблона, который реализуется с помощью вызовов реализации void *
, которую вы можете изменить по желанию.