Сохранение определений функций шаблона C++ в файле .CPP.

StackOverflow https://stackoverflow.com/questions/115703

  •  02-07-2019
  •  | 
  •  

Вопрос

У меня есть код шаблона, который я бы предпочел хранить в файле 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, но захочу перенести его в другие среды.

Это было полезно?

Решение

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

Рекомендую прочитать следующие пункты из Часто задаваемые вопросы по C++ Lite:

Они подробно рассказывают об этих (и других) проблемах с шаблонами.

Другие советы

Для других на этой странице, которые задаются вопросом, каков правильный синтаксис (как и я) для явной специализации шаблона (или, по крайней мере, в 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>;

Этот код правильно сформирован.Вам нужно только обратить внимание на то, что определение шаблона видно в момент создания экземпляра.Процитируем стандарт, § 14.7.2.4:

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

Это должно работать нормально везде, где поддерживаются шаблоны.Явное создание экземпляров шаблонов является частью стандарта C++.

Ваш пример правильный, но не очень переносимый.Существует также немного более чистый синтаксис, который можно использовать (как указано @namespace-sid).

Предположим, что шаблонный класс является частью некоторой библиотеки, которая должна быть общей.Следует ли скомпилировать другие версии шаблонного класса?Должен ли сопровождающий библиотеки предвидеть все возможные шаблонные варианты использования класса?

Альтернативный подход — это небольшая вариация того, что у вас есть:добавьте третий файл, который является файлом реализации/экземпляра шаблона.

файл foo.h

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};

файл foo.cpp

// Always include your headers
#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}

файл foo-impl.cpp

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;

Единственное предостережение: вам нужно указать компилятору, чтобы он скомпилировал foo-impl.cpp вместо foo.cpp поскольку компиляция последнего ничего не дает.

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

Это обеспечивает гораздо большую гибкость при совместном использовании шаблонного класса для других целей.

Эта настройка также сокращает время компиляции повторно используемых классов, поскольку вы не перекомпилируете один и тот же файл заголовка в каждой единице перевода.

Это определенно не плохой хак, но имейте в виду, что вам придется сделать это (явная специализация шаблона) для каждого класса/типа, который вы хотите использовать с данным шаблоном.Если МНОГИЕ типы запрашивают создание экземпляра шаблона, в вашем .cpp файле может быть МНОГО строк.Чтобы решить эту проблему, вы можете использовать TemplateClassInst.cpp в каждом используемом вами проекте, чтобы иметь больший контроль над тем, какие типы будут создаваться.Очевидно, что это решение не будет идеальным (так называемая серебряная пуля), поскольку в конечном итоге вы можете нарушить ODR :).

В последнем стандарте есть ключевое слово (export), это помогло бы решить эту проблему, но оно не реализовано ни в одном известном мне компиляторе, кроме Comeau.

См. FAQ-lite об этом.

Да, это стандартный способ сделать специализация явная реализация.Как вы заявили, вы не можете создать экземпляр этого шаблона с другими типами.

Редактировать:исправлено на основании комментария.

Это стандартный способ определения функций шаблона.Я думаю, что есть три метода определения шаблонов.Или, возможно, 4.Каждый со своими плюсами и минусами.

  1. Определите в определении класса.Мне это совсем не нравится, потому что я считаю, что определения классов предназначены исключительно для справки и должны быть легко читаемыми.Однако определить шаблоны внутри класса гораздо проще, чем снаружи.И не все объявления шаблонов находятся на одном уровне сложности.Этот метод также делает шаблон настоящим шаблоном.

  2. Определите шаблон в том же заголовке, но вне класса.В большинстве случаев это мой предпочтительный способ.Это сохраняет определение вашего класса в чистоте, шаблон остается настоящим шаблоном.Однако для этого требуется полное именование шаблона, что может быть непросто.Кроме того, ваш код доступен всем.Но если вам нужно, чтобы ваш код был встроенным, это единственный способ.Это также можно сделать, создав файл .INL в конце определений классов.

  3. Включите header.h иimplementation.CPP в свой main.CPP.Я думаю, что так это и делается.Вам не нужно будет готовить какие-либо предварительные экземпляры, он будет вести себя как настоящий шаблон.Моя проблема в том, что это неестественно.Обычно мы не включаем и не ожидаем включения исходных файлов.Я думаю, поскольку вы включили исходный файл, функции шаблона могут быть встроены.

  4. Этот последний метод, который был опубликован, определяет шаблоны в исходном файле, как и номер 3;но вместо включения исходного файла мы предварительно создаем экземпляры шаблонов, которые нам понадобятся.У меня нет проблем с этим методом, и иногда он бывает полезен.У нас есть один большой код, его встраивание не принесет пользы, поэтому просто поместите его в файл CPP.И если мы знаем общие экземпляры и можем их предопределить.Это избавляет нас от необходимости писать одно и то же по 5-10 раз.Преимущество этого метода заключается в том, что наш код остается проприетарным.Но я не рекомендую помещать в файлы CPP крошечные, часто используемые функции.Так как это снизит производительность вашей библиотеки.

Обратите внимание: я не знаю о последствиях раздутого obj-файла.

В приведенном вами примере нет ничего плохого.Но я должен сказать, что считаю, что хранить определения функций в файле cpp неэффективно.Я понимаю только необходимость разделить объявление и определение функции.

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

Время для обновления!Создайте встроенный файл (.inl или, возможно, любой другой) и просто скопируйте в него все свои определения.Обязательно добавьте шаблон над каждой функцией (template <typename T, ...>).Теперь вместо включения файла заголовка во встроенный файл вы делаете обратное.Включить встроенный файл после объявление вашего класса (#include "file.inl").

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

Давайте возьмем один пример, скажем, по какой-то причине вы хотите иметь класс шаблона:

//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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top