Хранение определений функций шаблона C ++ в файле .CPP - PullRequest
438 голосов
/ 22 сентября 2008

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

.h file

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, но хочу портировать на другие среды.

Ответы [ 11 ]

195 голосов
/ 22 сентября 2008

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

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

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

102 голосов
/ 19 декабря 2012

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

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

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

11 голосов
/ 22 сентября 2008

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

8 голосов
/ 23 декабря 2016

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

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

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

foo.h file

// 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, так как компиляция последнего ничего не делает.

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

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

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

5 голосов
/ 07 ноября 2011

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

4 голосов
/ 22 сентября 2008

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

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

3 голосов
/ 21 августа 2018

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

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

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

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

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

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

3 голосов
/ 22 сентября 2008

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

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

0 голосов
/ 22 марта 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...