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

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

Ответ 1

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

Я рекомендую прочитать следующие пункты из С++ FAQ Lite:

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

Ваш пример верный, но не очень переносимый. Существует также несколько более чистый синтаксис, который можно использовать (как указано в @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, поскольку компиляция последнего ничего не делает.

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

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

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

Ответ 6

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

Ответ 7

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

Смотрите FAQ-lite об этом.

Ответ 8

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

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

Ответ 9

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

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

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

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

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

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

Ответ 10

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

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

Ответ 11

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

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

Ответ 12

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

//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)" ([email protected][email protected]@@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.