Цитата из стандартной библиотеки C ++: учебное пособие и справочник :
Единственный переносимый способ использования шаблонов на данный момент - это реализовать их в заголовочных файлах с помощью встроенных функций.
Почему это?
(Пояснение: заголовочные файлы - не единственное переносимое решение. Но это наиболее удобное переносимое решение.)
Ответы:
Предостережение: это не необходимости помещать реализацию в заголовочный файл, см. Альтернативное решение в конце этого ответа.
В любом случае, причина вашего кода в том, что при создании экземпляра шаблона компилятор создает новый класс с заданным аргументом шаблона. Например:
При чтении этой строки компилятор создаст новый класс (назовем его
FooInt
), который эквивалентен следующему:Следовательно, компилятор должен иметь доступ к реализации методов, чтобы создавать их экземпляры с помощью аргумента шаблона (в данном случае
int
). Если бы эти реализации не были в заголовке, они не были бы доступны, и поэтому компилятор не смог бы создать экземпляр шаблона.Распространенным решением этой проблемы является запись объявления шаблона в файл заголовка, затем реализация класса в файле реализации (например, .tpp) и включение этого файла реализации в конец заголовка.
foo.h
Foo.tpp
Таким образом, реализация по-прежнему отделена от объявления, но доступна для компилятора.
Альтернативное решение
Другое решение состоит в том, чтобы отделить реализацию и явно создать экземпляры всех необходимых вам шаблонов:
foo.h
foo.cpp
Если мое объяснение недостаточно ясно, вы можете взглянуть на C ++ Super-FAQ по этому вопросу .
источник
Это связано с требованием отдельной компиляции и тем, что шаблоны являются полиморфизмом в стиле экземпляров.
Давайте немного приблизимся к конкретному для объяснения. Скажем, у меня есть следующие файлы:
class MyClass<T>
class MyClass<T>
MyClass<int>
Отдельная компиляция означает, что я должен иметь возможность компилировать foo.cpp независимо от bar.cpp . Компилятор выполняет всю тяжелую работу по анализу, оптимизации и генерации кода на каждом модуле компиляции полностью независимо; нам не нужно делать анализ всей программы. Только компоновщик должен обрабатывать всю программу одновременно, и работа компоновщика существенно проще.
bar.cpp даже не должен существовать, когда я компилирую foo.cpp , но я все еще должен иметь возможность связать foo.o, который я уже имел вместе с bar.o, который я только что произвел, без необходимости перекомпилировать foo .cpp . foo.cpp может даже быть скомпилирован в динамическую библиотеку, распространяться где-то еще без foo.cpp и связан с кодом, который они пишут спустя годы после того, как я написал foo.cpp .
«Полиморфизм в стиле реализации» означает, что шаблон на
MyClass<T>
самом деле не является универсальным классом, который можно скомпилировать в код, который может работать для любого значенияT
. Это было бы добавить накладные расходы , такие как бокс, необходимость передать указатели на функции для распределителей и конструкторами и т.д. Намерение шаблонов C ++, чтобы избежать необходимости писать почти идентичныclass MyClass_int
,class MyClass_float
и т.д., но по - прежнему быть в состоянии закончить с скомпилированного кода , который главным образом , как если бы мы были написаны каждую версию отдельно. Таким образом, шаблон буквально шаблон; шаблон класса - это не класс, это рецепт создания нового класса для каждого, с которымT
мы сталкиваемся. Шаблон не может быть скомпилирован в код, может быть скомпилирован только результат создания шаблона.Поэтому, когда foo.cpp компилируется, компилятор не может видеть bar.cpp, чтобы знать, что
MyClass<int>
это необходимо. Он может видеть шаблонMyClass<T>
, но не может генерировать код для этого (это шаблон, а не класс). И когда bar.cpp компилируется, компилятор видит, что ему нужно создатьMyClass<int>
, но он не может видеть шаблонMyClass<T>
(только его интерфейс в foo.h ), поэтому он не может его создать.Если сам foo.cpp использует
MyClass<int>
, то код для этого будет сгенерирован при компиляции foo.cpp , поэтому, когда bar.o связан с foo.o, они могут быть подключены и будут работать. Мы можем использовать этот факт, чтобы разрешить реализацию конечного набора шаблонов в файле .cpp, написав один шаблон. Но bar.cpp не может использовать шаблон в качестве шаблона и создавать его экземпляры для любых типов; он может использовать только существующие версии шаблонного класса, которые автор foo.cpp решил предоставить.Вы можете подумать, что при компиляции шаблона компилятор должен «генерировать все версии», а те, которые никогда не используются, отфильтровываются во время компоновки. Помимо огромных накладных расходов и чрезвычайных трудностей, с которыми столкнулся бы такой подход, потому что функции «модификатора типа», такие как указатели и массивы, позволяют даже только встроенным типам создавать бесконечное число типов, что происходит, когда я теперь расширяю свою программу добавлением:
class BazPrivate
, и используетMyClass<BazPrivate>
Это невозможно, если мы тоже
MyClass<T>
MyClass<T>
, чтобы компилятор мог генерироватьMyClass<BazPrivate>
во время компиляции baz.cpp .Никому не нравится (1), потому что системам компиляции анализа всей программы требуется вечность для компиляции, и потому что это делает невозможным распространение скомпилированных библиотек без исходного кода. Таким образом, мы имеем (2) вместо этого.
источник
Здесь много правильных ответов, но я хотел бы добавить это (для полноты):
Если вы в нижней части файла cpp реализации сделаете явное создание экземпляров всех типов, с которыми будет использоваться шаблон, компоновщик сможет найти их как обычно.
Редактировать: Добавление примера явного создания шаблона. Используется после определения шаблона и определения всех функций-членов.
Это создаст экземпляр (и, следовательно, сделает доступным для компоновщика) класс и все его функции-члены (только). Аналогичный синтаксис работает для шаблонных функций, поэтому если у вас есть перегрузки операторов, не являющихся членами, вам может потребоваться сделать то же самое для них.
Приведенный выше пример довольно бесполезен, поскольку вектор полностью определен в заголовках, за исключением случаев, когда используется общий включаемый файл (предварительно скомпилированный заголовок?),
extern template class vector<int>
Чтобы он не создавал его экземпляры во всех других (1000?) Файлах, которые используют вектор.источник
type
без их ручного перечисления.vector
Это не хороший пример, потому что контейнер по своей природе нацелен на «все» типы. Но очень часто случается, что вы создаете шаблоны, которые предназначены только для определенного набора типов, например числовых типов: int8_t, int16_t, int32_t, uint8_t, uint16_t и т. Д. В этом случае все же имеет смысл использовать шаблон , но явное создание их экземпляров для всего набора типов также возможно и, на мой взгляд, рекомендуется..cpp
файл класса, и два экземпляра ссылаются из других.cpp
файлов, и я все еще получаю ошибку компоновки, что члены не найдены.Шаблоны должны быть созданы компилятором перед тем, как фактически скомпилировать их в объектный код. Эта реализация может быть достигнута только в том случае, если известны аргументы шаблона. Теперь представьте сценарий, в котором функция шаблона объявлена
a.h
, определенаa.cpp
и используется вb.cpp
. Когдаa.cpp
компилируется, не обязательно известно, что для предстоящей компиляцииb.cpp
потребуется экземпляр шаблона, не говоря уже о том, какой конкретный экземпляр будет. Для большего количества заголовочных и исходных файлов ситуация может быстро усложниться.Можно утверждать, что компиляторы могут быть умнее, чтобы «смотреть в будущее» для всех применений шаблона, но я уверен, что не будет трудно создавать рекурсивные или иные сложные сценарии. AFAIK, компиляторы не делают такой взгляд вперед. Как указал Антон, некоторые компиляторы поддерживают явные объявления экспорта экземпляров шаблона, но не все компиляторы поддерживают его (пока?).
источник
Фактически, до C ++ 11 стандарт определял
export
ключевое слово, которое позволяло бы объявлять шаблоны в заголовочном файле и реализовывать их в другом месте.Ни один из популярных компиляторов не реализовал это ключевое слово. Единственный, о котором я знаю, - это интерфейс, написанный Edison Design Group, который используется компилятором Comeau C ++. Все остальные требуют, чтобы вы писали шаблоны в заголовочных файлах, потому что компилятору нужно определение шаблона для правильной реализации (как уже указывали другие).
В результате комитет по стандарту ISO C ++ решил удалить
export
функцию шаблонов в C ++ 11.источник
export
самом деле дало бы нам, а что нет ... и теперь я искренне согласен с людьми из EDG: это не принесло бы нам то, что большинство людей (я сам в '11 включил) думаю, что будет, и стандарт C ++ лучше без него.Хотя в стандарте C ++ такого требования нет, некоторые компиляторы требуют, чтобы все шаблоны функций и классов были доступны в каждом используемом модуле перевода. По сути, для этих компиляторов тела шаблонных функций должны быть доступны в заголовочном файле. Повторим: это означает, что эти компиляторы не позволят им быть определены в файлах без заголовка, таких как файлы .cpp
Существует ключевое слово экспорта, которое должно смягчить эту проблему, но оно далеко от того, чтобы быть переносимым.
источник
Шаблоны должны использоваться в заголовках, потому что компилятор должен создавать различные версии кода в зависимости от параметров, заданных / выведенных для параметров шаблона. Помните, что шаблон не представляет код напрямую, а шаблон для нескольких версий этого кода. Когда вы компилируете не шаблонную функцию в
.cpp
файле, вы компилируете конкретную функцию / класс. Это не относится к шаблонам, которые могут быть созданы с различными типами, а именно, конкретный код должен генерироваться при замене параметров шаблона на конкретные типы.Была функция с
export
ключевым словом, которая должна была использоваться для отдельной компиляции. Этаexport
функция устарела,C++11
и, AFAIK, ее реализовал только один компилятор. Вы не должны использоватьexport
. Раздельная компиляция невозможнаC++
или,C++11
может бытьC++17
, если концепты делают это, у нас может быть какой-то способ раздельной компиляции.Для отдельной компиляции должна быть возможна отдельная проверка тела шаблона. Кажется, что решение возможно с концепциями. Взгляните на этот документ, недавно представленный на заседании комитета по стандартам. Я думаю, что это не единственное требование, так как вам все еще нужно создать экземпляр кода для шаблона в пользовательском коде.
Отдельная проблема компиляции для шаблонов. Думаю, это также проблема, возникающая при миграции на модули, которая в настоящее время работает.
источник
Это означает, что наиболее переносимым способом определения реализаций методов шаблонных классов является определение их внутри определения класса шаблона.
источник
Несмотря на множество хороших объяснений выше, мне не хватает практического способа разделения шаблонов на заголовок и тело.
Моя главная задача - избегать перекомпиляции всех пользователей шаблона, когда я изменяю его определение.
Наличие всех экземпляров шаблона в теле шаблона не является для меня жизнеспособным решением, так как автор шаблона может не знать все, если он используется, и пользователь шаблона может не иметь права изменять его.
Я выбрал следующий подход, который работает и для старых компиляторов (gcc 4.3.4, aCC A.03.13).
Для каждого использования шаблона есть typedef в своем собственном заголовочном файле (создан из модели UML). Его тело содержит экземпляр (который заканчивается в библиотеке, которая связана в конце).
Каждый пользователь шаблона включает этот заголовочный файл и использует typedef.
Схематический пример:
MyTemplate.h:
MyTemplate.cpp:
MyInstantiatedTemplate.h:
MyInstantiatedTemplate.cpp:
main.cpp:
Таким образом, потребуется перекомпиляция только экземпляров шаблона, а не всех пользователей шаблона (и зависимостей).
источник
MyInstantiatedTemplate.h
файла и добавленногоMyInstantiatedTemplate
типа. Немного чище, если ты им не пользуешься, имхо. Зацените мой ответ на другой вопрос, показывающий это: stackoverflow.com/a/41292751/4612476Просто чтобы добавить что-то примечательное здесь. Можно просто определить методы шаблонного класса в файле реализации, когда они не являются шаблонами функций.
myQueue.hpp:
myQueue.cpp:
источник
isEmpty
из других переводчиков, кромеmyQueue.cpp
...Если проблема заключается в дополнительном времени компиляции и разложении двоичного размера, создаваемом путем компиляции .h как части всех модулей .cpp, использующих его, во многих случаях вы можете сделать так, чтобы класс шаблона происходил из базового класса без шаблонов для нетипозависимые части интерфейса, и этот базовый класс может иметь свою реализацию в файле .cpp.
источник
class XBase
везде, где мне нужно реализоватьtemplate class X
, помещая зависимые от типа частиX
и все остальноеXBase
.Это совершенно правильно, потому что компилятор должен знать, какой он тип для выделения. Таким образом, классы шаблонов, функции, перечисления и т. Д. Также должны быть реализованы в заголовочном файле, если он должен быть общедоступным или частью библиотеки (статической или динамической), поскольку заголовочные файлы НЕ компилируются в отличие от файлов c / cpp, которые находятся. Если компилятор не знает, тип не может скомпилировать его. В .Net это возможно, потому что все объекты являются производными от класса Object. Это не .Net.
источник
Компилятор будет генерировать код для каждого экземпляра шаблона при использовании шаблона на этапе компиляции. В процессе компиляции и компоновки файлы .cpp преобразуются в чистый объектный или машинный код, который содержит ссылки или неопределенные символы, поскольку файлы .h, включенные в ваш main.cpp, не имеют реализации YET. Они готовы к связыванию с другим объектным файлом, который определяет реализацию для вашего шаблона, и, таким образом, у вас есть полный исполняемый файл a.out.
Однако, поскольку шаблоны необходимо обрабатывать на этапе компиляции, чтобы генерировать код для каждого экземпляра шаблона, который вы определяете, поэтому простая компиляция шаблона отдельно от его заголовочного файла не будет работать, потому что они всегда идут рука об руку по самой причине что каждый экземпляр шаблона является совершенно новым классом буквально. В обычном классе вы можете разделить .h и .cpp, потому что .h является планом этого класса, а .cpp - это необработанная реализация, поэтому любые файлы реализации можно регулярно компилировать и связывать, однако использование шаблонов .h - это план того, как класс должен выглядеть не так, как должен выглядеть объект, а это означает, что файл .cpp шаблона не является обычной обычной реализацией класса, это просто план для класса, поэтому любая реализация файла шаблона .h может
Поэтому шаблоны никогда не компилируются отдельно и компилируются только там, где у вас есть конкретный экземпляр в каком-либо другом исходном файле. Тем не менее, конкретный экземпляр должен знать реализацию файла шаблона, потому что просто изменив
typename T
использование конкретного типа в файле .h не поможет, потому что там есть ссылки на .cpp, я не могу найти их позже, потому что помните, что шаблоны абстрактны и не могут быть скомпилированы, поэтому я вынужден дать реализацию прямо сейчас, чтобы я знал, что компилировать и связывать, и теперь, когда у меня есть реализация, она связывается с прилагаемым исходным файлом. По сути, в тот момент, когда я создаю экземпляр шаблона, мне нужно создать целый новый класс, и я не могу этого сделать, если не знаю, как должен выглядеть этот класс при использовании предоставляемого мной типа, если я не сделаю уведомления компилятору реализация шаблона, так что теперь компилятор может заменитьT
мой тип и создать конкретный класс, который готов к компиляции и компоновке.Подводя итог, шаблоны - это чертежи того, как должны выглядеть классы, а классы - это чертежи того, как должен выглядеть объект. Я не могу скомпилировать шаблоны отдельно от их конкретной реализации, потому что компилятор компилирует только конкретные типы, иными словами, шаблоны, по крайней мере, в C ++, являются чистой языковой абстракцией. Мы должны де-абстрагировать шаблоны, так сказать, и делаем это, давая им конкретный тип для работы, чтобы наша абстракция шаблона могла трансформироваться в обычный файл класса и, в свою очередь, может быть скомпилирована нормально. Разделение файла шаблона .h и файла шаблона .cpp не имеет смысла. Это бессмысленно, потому что разделение .cpp и .h происходит только тогда, когда .cpp можно скомпилировать по отдельности и связать индивидуально, с помощью шаблонов, поскольку мы не можем скомпилировать их отдельно, потому что шаблоны являются абстракцией,
Это означает, что
typename T
get заменяется на этапе компиляции, а не на этапе компоновки, поэтому, если я попытаюсь скомпилировать шаблон безT
замены в качестве конкретного типа значения, который абсолютно бессмыслен для компилятора, и, как результат, код объекта не может быть создан, потому что он не создает знать чтоT
есть.Технически возможно создать какую-то функциональность, которая сохранит файл template.cpp и переключит типы, когда он найдет их в других источниках. Я думаю, что в стандарте есть ключевое слово
export
, которое позволит вам размещать шаблоны в отдельном файле. Cpp файл, но не так много компиляторов фактически реализуют это.Просто примечание: при создании специализаций для шаблонного класса вы можете отделить заголовок от реализации, потому что специализация по определению означает, что я специализируюсь на конкретном типе, который можно скомпилировать и связать по отдельности.
источник
Способ иметь отдельную реализацию заключается в следующем.
inner_foo имеет предварительные объявления. foo.tpp имеет реализацию и включает inner_foo.h; и foo.h будет содержать только одну строку, чтобы включить foo.tpp.
Во время компиляции содержимое файла foo.h копируется в foo.tpp, а затем весь файл копируется в файл foo.h, после чего он компилируется. Таким образом, нет никаких ограничений, и наименование является последовательным, в обмен на один дополнительный файл.
Я делаю это потому, что статические анализаторы кода ломаются, когда он не видит предварительные объявления класса в * .tpp. Это раздражает, когда вы пишете код в любой IDE или используете YouCompleteMe или другие.
источник
Я предлагаю посмотреть на эту страницу gcc, на которой обсуждаются компромиссы между "cfront" и "borland" моделью для создания шаблонов.
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
Модель "borland" соответствует тому, что предлагает автор, предоставляя полное определение шаблона и составляя объекты несколько раз.
Он содержит четкие рекомендации по использованию ручного и автоматического создания шаблона. Например, опция «-repo» может использоваться для сбора шаблонов, которые необходимо создать. Другой вариант - отключить автоматическое создание экземпляров шаблона с помощью «-fno-implicit-templates», чтобы принудительно создать экземпляр шаблона вручную.
Исходя из своего опыта, я полагаюсь на то, что стандартные библиотеки C ++ и шаблоны Boost создаются для каждого модуля компиляции (с использованием библиотеки шаблонов). Для моих больших шаблонных классов я выполняю ручную инстанцирование шаблонов один раз для нужных мне типов.
Это мой подход, потому что я предоставляю рабочую программу, а не библиотеку шаблонов для использования в других программах. Автор книги, Josuttis, много работает над библиотеками шаблонов.
Если бы я действительно беспокоился о скорости, я полагаю, что я бы изучил использование предварительно скомпилированных заголовков https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html.
которая получает поддержку во многих компиляторах. Тем не менее, я думаю, что скомпилированные заголовки будет сложно с файлами заголовков шаблонов.
источник
Еще одна причина, по которой в заголовочные файлы рекомендуется записывать как объявления, так и определения, - удобство чтения. Предположим, в Utility.h есть такая шаблонная функция:
И в Utility.cpp:
Это требует, чтобы каждый класс T здесь реализовывал оператор less than (<). Он сгенерирует ошибку компилятора, когда вы сравните два экземпляра класса, которые не реализовали «<».
Поэтому, если вы разделите объявление и определение шаблона, вы не сможете только прочитать файл заголовка, чтобы увидеть все входы и выходы этого шаблона, чтобы использовать этот API в своих собственных классах, хотя компилятор скажет вам об этом случай, о котором оператор должен быть переопределен.
источник
Вы можете фактически определить свой шаблонный класс внутри файла .template, а не файла .cpp. Тот, кто говорит, что вы можете определить его только внутри заголовочного файла, ошибается. Это то, что работает вплоть до C ++ 98.
Не забывайте, чтобы ваш компилятор рассматривал ваш файл .template как файл c ++, чтобы сохранить смысл intelli.
Вот пример этого для класса динамического массива.
Теперь внутри вашего файла .template вы определяете свои функции так, как вы это обычно делаете.
источник