У меня есть код шаблона, который я бы предпочел хранить в файле CPP вместо встроенного в заголовок. Я знаю, что это можно сделать, если вы знаете, какие типы шаблонов будут использоваться. Например:
.h файл
class foo
{
public:
template <typename T>
void do(const T& t);
};
.cpp файл
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Обратите внимание на последние две строки - функция шаблона foo :: do используется только с ints и std :: strings, поэтому эти определения означают, что приложение будет связываться.
У меня вопрос - это неприятный хакер или это будет работать с другими компиляторами / компоновщиками? В настоящий момент я использую этот код только с VS2008, но хочу перенести его в другие среды.
Меня поражает использование do в качестве идентификатора: p
я сделал что-то подобное с gcc, но все еще исследую
Это не «взлом», это предварительное объявление. Этому есть место в стандарте языка; так что да, это разрешено в каждом стандартном компиляторе.
Что, если у вас есть десятки методов? Можете просто сделать template class foo<int>;template class foo<std::string>; в конце файла .cpp?
связанные: stackoverflow.com/questions/495021/…





Да, это стандартный способ явного создания экземпляра специализация. Как вы заявили, вы не можете создать экземпляр этого шаблона с другими типами.
Обновлено: исправлено на основе комментария.
Придирчиво к терминологии, это «явное воплощение».
Описанная вами проблема может быть решена путем определения шаблона в заголовке или с помощью подхода, который вы описали выше.
Я рекомендую прочитать следующие пункты из C++ FAQ Lite:
Они подробно рассказывают об этих (и других) проблемах с шаблоном.
Чтобы дополнить ответ, указанная ссылка дает положительный ответ на вопрос, т.е. можно делать то, что предложил Роб, и иметь переносимый код.
Можете ли вы просто опубликовать соответствующие части в самом ответе? Почему такие ссылки разрешены даже на SO. Я понятия не имею, что искать в этой ссылке, поскольку с тех пор она сильно изменилась.
Это должно работать нормально везде, где поддерживаются шаблоны. Явное создание шаблона является частью стандарта C++.
В последнем стандарте есть ключевое слово (export), которое помогло бы решить эту проблему, но оно не реализовано ни в одном известном мне компиляторе, кроме Comeau.
См. FAQ-lite об этом.
AFAIK, экспорт мертв, потому что они сталкиваются с все новыми и новыми проблемами, каждый раз они решают последнюю, что делает общее решение все более и более сложным. И ключевое слово «экспорт» в любом случае не позволит вам «экспортировать» из CPP (все равно из Х. Саттера). Поэтому я говорю: не задерживайте дыхание ...
Для реализации экспорта компилятору по-прежнему требуется полное определение шаблона. Все, что вы получаете, - это иметь его в некотором роде откомпилированной формы. Но на самом деле в этом нет никакого смысла.
... и это ушел от стандарта из-за чрезмерного усложнения для минимального усиления.
Этот код хорошо сформирован. Вам нужно только обратить внимание на то, что определение шаблона видно в момент создания экземпляра. Процитируем стандарт, § 14.7.2.4:
The definition of a non-exported function template, a non-exported member function template, or a non-exported member function or static data member of a class template shall be present in every translation unit in which it is explicitly instantiated.
Что означает не экспортированный?
@Dan Виден только внутри модуля компиляции, но не вне его. Если вы связываете несколько единиц компиляции вместе, экспортируемые символы могут использоваться в них (и должны иметь одно или, по крайней мере, в случае шаблонов, согласованные определения, иначе вы столкнетесь с UB).
Спасибо. Я думал, что все функции (по умолчанию) видны вне модуля компиляции. Если у меня есть две единицы компиляции a.cpp (определяющие функцию a() {}) и b.cpp (определяющие функцию b() { a() }), то это будет успешно соединено. Если я прав, то приведенная выше цитата не подходит для типичного случая ... Я где-то ошибаюсь?
@Dan Тривиальный контрпример: функции inline
Для функций экспортируется (типичный случай, если я правильно понимаю), однако, из цитируемого абзаца, не было бы тогда правдой, что определение функции шаблона действительно, нет должно присутствовать в блоке перевода, в котором функция шаблона явно создан? Если это так, это, по-видимому, нарушает значение принятого ответа, поскольку ответ заключается в том, что это правильный подход для явного создания экземпляра функции шаблона в исходном файле такой же (и, следовательно, в той же единице перевода), в которой она определена. Если у вас есть время, я хотел бы это прояснить! Спасибо.
Шаблоны функций @Dan неявно являются inline. Причина в том, что без стандартизированного C++ ABI трудно / невозможно определить эффект, который в противном случае имел бы место.
осторожно, рекурсивные вопросы и ответы :)
В приведенном вами примере нет ничего плохого. Но я должен сказать, что считаю неэффективным хранить определения функций в файле cpp. Я понимаю только необходимость разделять объявление и определение функции.
При использовании вместе с явным созданием экземпляров класса библиотека проверки концепции Boost (BCCL) может помочь вам сгенерировать код функции шаблона в файлах cpp.
Что в этом неэффективного?
Это определенно не неприятный прием, но имейте в виду, что вам придется делать это (явная специализация шаблона) для каждого класса / типа, который вы хотите использовать с данным шаблоном. В случае МНОГИХ типов, запрашивающих создание шаблона, в вашем .cpp файле может быть МНОГО строк. Чтобы решить эту проблему, вы можете иметь TemplateClassInst.cpp в каждом используемом вами проекте, чтобы иметь больший контроль над тем, какие типы будут созданы. Очевидно, что это решение не будет идеальным (он же серебряная пуля), так как вы можете нарушить ODR :).
Вы уверены, что это нарушит ODR? Если строки создания экземпляров в TemplateClassInst.cpp ссылаются на идентичный исходный файл (содержащий определения функций шаблона), разве это не гарантирует нарушения ODR, поскольку все определения идентичны (даже если они повторяются)?
Пожалуйста, что такое ODR?
Для других на этой странице, которым интересно, каков правильный синтаксис (как и я) для явной специализации шаблона (или, по крайней мере, в VS2008), это следующее ...
В вашем .h файле ...
template<typename T>
class foo
{
public:
void bar(const T &t);
};
И в вашем файле .cpp
template <class T>
void foo<T>::bar(const T &t)
{ }
// Explicit template instantiation
template class foo<int>;
Вы имеете в виду "для явного использования шаблона CLASS". В таком случае будет ли это охватывать все функции, которые есть в шаблонном классе?
@Arthur, похоже, нет, у меня некоторые методы шаблона остаются в заголовке, а большинство других методов в cpp работают нормально. Очень красивое решение.
В случае спрашивающего у них есть шаблон функции, а не шаблон класса.
Итак, вы можете поместить несколько классов шаблонов foo <...> в конец определенного файла, верно? Итак, один файл для определений для int, например, Other для float, если есть какие-то различия, если нет различий, вы можете просто перенести класс шаблона foo <float> под int? Я правильно понимаю?
Пришло время обновления! Создайте встроенный (.inl или, возможно, любой другой) файл и просто скопируйте в него все свои определения. Не забудьте добавить шаблон над каждой функцией (template <typename T, ...>). Теперь вместо включения файла заголовка во встроенный файл вы делаете обратное. Включите встроенный файл после в объявление вашего класса (#include "file.inl").
Я действительно не знаю, почему никто не упомянул об этом. Сразу недостатков не вижу.
Непосредственный недостаток заключается в том, что это в основном то же самое, что и определение функций шаблона непосредственно в заголовке. После того, как вы запустите #include "file.inl", препроцессор вставит содержимое file.inl прямо в заголовок. По какой бы причине вы не хотели, чтобы реализация была указана в заголовке, это решение не решает эту проблему.
- и означает, что вы технически без надобности обременяете себя задачей написания всех подробных, умопомрачительных шаблонов, необходимых для автономных определений template. Я понимаю, почему люди хотят это делать - для достижения максимальной четкости с объявлениями / определениями, не являющимися шаблонами, чтобы объявление интерфейса выглядело аккуратно и т. д. - но это не всегда стоит хлопот. Это случай оценки компромиссов с обеих сторон и выбора наименее плохо. ... пока namespace class не станет вещью: O [пожалуйста, будь вещью]
@ Андрей: Кажется, он застрял в трубе Комитета, хотя, кажется, я видел, как кто-то говорил, что это не было намеренно. Хотелось бы, чтобы это вошло в C++ 17. Может, в следующем десятилетии.
@CodyGray: Технически это действительно то же самое для компилятора и, следовательно, не сокращает время компиляции. Тем не менее, я думаю, что это стоит упомянуть и практиковать в ряде проектов, которые я видел. Этот путь помогает отделить интерфейс от определения, что является хорошей практикой. В этом случае это не помогает с совместимостью ABI и т.п., но облегчает чтение и понимание интерфейса.
Ваш пример правильный, но не очень переносимый. Существует также немного более чистый синтаксис, который можно использовать (как указано, среди прочего, @ namespace-sid).
Однако предположим, что шаблонный класс является частью некоторой библиотеки, которая будет использоваться совместно ...
Следует ли компилировать другие версии шаблонного класса?
Должен ли сопровождающий библиотеки предвидеть все возможные варианты использования этого класса по шаблону?
Добавьте третий файл, который является файлом реализации / создания шаблона в ваших источниках.
lib/foo.hppв / из библиотеки
#pragma once
template <typename T>
class foo
{
public:
void bar(const T&);
};
lib/foo.cppкомпиляция этого файла напрямую просто тратит время компиляции
// Include guard here, just in case
#pragma once
#include "foo.hpp"
template <typename T>
void foo::bar(const T& arg)
{
// Do something with `arg`
}
foo.MyType.cppс использованием библиотеки, явное создание экземпляра шаблона foo<MyType>
// Consider adding "anti-guard" to make sure it's not included in other translation units
#if __INCLUDE_LEVEL__ != 0
#error "Don't include this file"
#endif
// Yes, we include the .cpp file
#include <lib/foo.cpp>
#include "MyType.hpp"
template class foo<MyType>;
Конечно, в третьем файле может быть несколько реализаций. Или вам может понадобиться несколько файлов реализации, например, по одному для каждого типа (или набора типов), который вы хотите использовать.
Эта настройка должна сократить время компиляции, особенно для часто используемого сложного шаблонного кода, потому что вы не перекомпилируете один и тот же файл заголовка в каждом единица перевода. Это также позволяет компиляторам и сценариям сборки лучше определять, какой код необходимо перекомпилировать, что снижает нагрузку на инкрементную сборку.
foo.declarations.hppнеобходимо знать об общедоступном интерфейсе foo<MyType>, но не об источниках .cpp
#pragma once
#include <lib/foo.hpp>
#include "MyType.hpp"
// Declare `temp`. Doesn't need to include `foo.cpp`
extern foo<MyType> temp;
examples.cppможет ссылаться на локальную декларацию, но также не перекомпилирует foo<MyType>
#include "foo.declarations.hpp"
MyType instance;
// Define `temp`. Doesn't need to include `foo.cpp`
foo<MyType> temp;
void example_1() {
// Use `temp`
temp.bar(instance);
}
void example_2() {
// Function local instance
foo<MyType> temp2;
// Use templated library function
temp2.bar(instance);
}
error.cppпример, который будет работать с чистыми шаблонами заголовков, но не здесь
#include <lib/foo.hpp>
// Causes compilation errors at link time since we never had the explicit instantiation:
// template class foo<int>;
// GCC linker gives an error: "undefined reference to `foo<int>::bar()'"
void linkerError()
{
foo<int> nonExplicitlyInstantiatedTemplate;
}
Обратите внимание, что большинство компиляторов / линтеров / помощников по коду не распознают это как ошибку, поскольку в соответствии со стандартом C++ ошибки нет.
Но когда вы собираетесь связать эту единицу трансляции с полным исполняемым файлом, компоновщик не найдет определенную версию foo<int>.
что это тебе дает? Вам все равно нужно отредактировать foo-impl.cpp, чтобы добавить новую специализацию.
Разделение деталей реализации (также называемых определениями в foo.cpp), из которых фактически компилируются версии (в foo-impl.cpp) и деклараций (в foo.h). Мне не нравится, что большинство шаблонов C++ полностью определены в файлах заголовков. Это противоречит стандарту C / C++ пар c[pp]/h для каждого класса / пространства имен / любой группы, которую вы используете. Люди, кажется, до сих пор используют монолитные файлы заголовков просто потому, что эта альтернатива широко не используется и не известна.
@MK. Сначала я помещал явные экземпляры шаблона в конец определения в исходном файле, пока мне не потребовались дальнейшие экземпляры в другом месте (например, модульные тесты с использованием макета в качестве шаблонного типа). Это разделение позволяет мне добавлять больше экземпляров извне. Кроме того, он все еще работает, когда я сохраняю оригинал как пару h/cpp, хотя мне пришлось окружить исходный список экземпляров защитой включения, но я все еще мог скомпилировать foo.cpp как обычно. Я все еще новичок в C++, и мне было бы интересно узнать, есть ли у этого смешанного использования какие-либо дополнительные оговорки.
Я считаю, что лучше разделять foo.cpp и foo-impl.cpp. Не используйте #include "foo.cpp" в файле foo-impl.cpp; вместо этого добавьте объявление extern template class foo<int>; в foo.cpp, чтобы компилятор не создавал экземпляр шаблона при компиляции foo.cpp. Убедитесь, что система сборки создает оба файла .cpp и передает оба объектных файла компоновщику. У этого есть несколько преимуществ: a) в foo.cpp ясно, что нет создания экземпляра; б) изменения в foo.cpp не требуют перекомпиляции foo-impl.cpp.
Это очень хороший подход к проблеме определений шаблонов, который берет лучшее из обоих миров - реализацию заголовка и создание экземпляров для часто используемых типов. Единственное изменение, которое я бы внес в эту настройку, - это переименовать foo.cpp в foo_impl.h, а foo-impl.cpp - в foo.cpp. Я бы также добавил typedef для экземпляров от foo.cpp до foo.h, аналогично using foo_int = foo<int>;. Хитрость заключается в том, чтобы предоставить пользователям два интерфейса заголовков на выбор. Когда пользователю требуется предопределенная реализация, он включает foo.h, когда пользователю нужно что-то не в порядке, он включает foo_impl.h.
Это стандартный способ определения функций шаблона. Я думаю, что есть три метода определения шаблонов, которые я прочитал. А может, 4. У каждого свои плюсы и минусы.
Определите в определении класса. Мне это совсем не нравится, потому что я думаю, что определения классов предназначены исключительно для справки и должны быть легко читаемыми. Однако определить шаблоны в классе гораздо проще, чем вне его. И не все объявления шаблонов имеют одинаковый уровень сложности. Этот метод также делает шаблон настоящим шаблоном.
Определите шаблон в том же заголовке, но вне класса. В большинстве случаев это мой предпочтительный способ. Это сохраняет ваше определение класса аккуратным, шаблон остается истинным шаблоном. Однако для этого требуется полное именование шаблона, что может быть непросто. Также ваш код доступен всем. Но если вам нужно, чтобы ваш код был встроенным, это единственный способ. Этого также можно добиться, создав файл .INL в конце определений классов.
Включите header.h и implementation.CPP в свой main.CPP. Думаю, вот как это делается. Вам не нужно будет готовить какие-либо предварительные экземпляры, он будет вести себя как настоящий шаблон. Проблема в том, что это неестественно. Обычно мы не включаем и не ожидаем включения исходных файлов. Я думаю, что, поскольку вы включили исходный файл, функции шаблона могут быть встроены.
Этот последний метод, который был опубликован, определяет шаблоны в исходном файле, как и номер 3; но вместо включения исходного файла мы предварительно создаем экземпляры шаблонов, которые нам понадобятся. У меня нет проблем с этим методом, и он иногда бывает полезен. У нас есть один большой код, он не может выиграть от встраивания, поэтому просто поместите его в файл CPP. И если мы знаем общие экземпляры и можем их предопределить. Это избавляет нас от необходимости писать одно и то же 5-10 раз. Преимущество этого метода заключается в том, что наш код остается закрытым. Но я не рекомендую помещать крошечные, регулярно используемые функции в файлы CPP. Поскольку это снизит производительность вашей библиотеки.
Обратите внимание, я не знаю о последствиях раздутого файла obj.
Возьмем один пример, допустим, по какой-то причине вы хотите иметь шаблонный класс:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
Если скомпилировать этот код с помощью Visual Studio - он работает из коробки. gcc выдаст ошибку компоновщика (если один и тот же файл заголовка используется из нескольких файлов .cpp):
error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here
Можно переместить реализацию в файл .cpp, но тогда вам нужно объявить класс следующим образом:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
template <>
void DemoT<int>::test();
template <>
void DemoT<bool>::test();
// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;
И тогда .cpp будет выглядеть так:
//test_template.cpp:
#include "test_template.h"
template <>
void DemoT<int>::test()
{
printf("int test (int)\n");
}
template <>
void DemoT<bool>::test()
{
printf("int test (bool)\n");
}
Без двух последних строк в файле заголовка - gcc будет работать нормально, но Visual Studio выдаст ошибку:
error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function
Синтаксис класса шаблона является необязательным, если вы хотите предоставить функцию через экспорт .dll, но это применимо только для платформы Windows, поэтому test_template.h может выглядеть так:
//test_template.h:
#pragma once
#include <cstdio>
template <class T>
class DemoT
{
public:
void test()
{
printf("ok\n");
}
};
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
template <>
void DLL_EXPORT DemoT<int>::test();
template <>
void DLL_EXPORT DemoT<bool>::test();
с файлом .cpp из предыдущего примера.
Однако это создает большую головную боль компоновщику, поэтому рекомендуется использовать предыдущий пример, если вы не экспортируете функцию .dll.
Отличный ответ
Ничего из вышеперечисленного у меня не сработало, поэтому вот как я это решил, в моем классе есть только один шаблонный метод.
.час
class Model
{
template <class T>
void build(T* b, uint32_t number);
};
.cpp
#include "Model.h"
template <class T>
void Model::build(T* b, uint32_t number)
{
//implementation
}
void TemporaryFunction()
{
Model m;
m.build<B1>(new B1(),1);
m.build<B2>(new B2(), 1);
m.build<B3>(new B3(), 1);
}
это позволяет избежать ошибок компоновщика и вообще не вызывать TemporaryFunction
Ваш ответ такой же, как и вопрос, и он не работает!
Я понятия не имел, что это возможно - интересный трюк! Знание этого могло бы значительно помочь некоторым недавним задачам - ура!