Разделение шаблонных классов C++ на.ТЭЦ./cpp файлов-это возможно?
Я получаю ошибки, пытаясь скомпилировать класс шаблона C++, который разделен между .hpp и .cpp file:
$ g++ -c -o main.o main.cpp
$ g++ -c -o stack.o stack.cpp
$ g++ -o main main.o stack.o
main.o: In function `main':
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()'
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'
collect2: ld returned 1 exit status
make: *** [program] Error 1
вот мой код:
стек.ГЭС:
#ifndef _STACK_HPP
#define _STACK_HPP
template <typename Type>
class stack {
public:
stack();
~stack();
};
#endif
стек.cpp:
#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
главная.cpp:
#include "stack.hpp"
int main() {
stack<int> s;
return 0;
}
ld конечно правильно: символы не в stack.o.
ответ этот вопрос тут не поможет, как я уже делаю, как он говорит.
этот может помочь, но я не хочу, чтобы переместить каждый способ в .hpp файл-я не должен был, не так ли?
- это единственное разумное решение, чтобы переместить все в до .hpp файл, и просто включить все, а не ссылку в качестве отдельного объектного файла? Это кажется ужасно некрасиво! В этом случае я мог бы также вернуться к своему предыдущему состоянию и переименовать stack.cpp до stack.hpp и покончим с этим.
16 ответов:
невозможно записать реализацию класса шаблона в отдельный cpp-файл и скомпилировать. Все способы сделать это, если кто-то утверждает, являются обходными путями, чтобы имитировать использование отдельного файла cpp, но практически, если вы собираетесь написать библиотеку классов шаблонов и распространять ее с заголовочными и lib-файлами, чтобы скрыть реализацию, это просто невозможно.
чтобы узнать, почему, давайте посмотрим на процесс компиляции. Заголовочные файлы не компилируются. Они только предварительно обрабатывать. Предварительно обработанный код затем объединяется с файлом cpp, который фактически компилируется. Теперь, если компилятор должен создать соответствующий макет памяти для объекта, он должен знать тип данных класса шаблона.
на самом деле необходимо понимать, что класс template - это не класс вообще, а шаблон для класса, объявление и определение которого генерируется компилятором во время компиляции после получения информации о типе данных из аргумент. Пока макет памяти не может быть создан, инструкции для определения метода не могут быть созданы. Помните, что первым аргументом метода класса является оператор 'this'. Все методы класса преобразуются в отдельные методы с именем mangling и первым параметром в качестве объекта, с которым он работает. Аргумент "this" фактически говорит о размере объекта, который упаковывает класс шаблона, недоступен для компилятора, если пользователь не создает экземпляр объекта с аргументом типа. В этом случае, если вы поместите определения методов в отдельный файл cpp и попытаетесь скомпилировать его, сам объектный файл не будет сгенерирован с информацией о классе. Компиляция не завершится неудачей, она будет генерировать объектный файл, но она не будет генерировать код для класса шаблона в объектном файле. Именно по этой причине компоновщик не может найти символы в объектных файлах, и сборка завершается неудачно.
теперь какова альтернатива, чтобы скрыть важные детали реализации? Как мы все знаем, основная цель отделения интерфейса от реализации прячет детали реализации в двоичной форме. Здесь необходимо разделить структуры данных и алгоритмы. Ваши классы шаблонов должны представлять только структуры данных, а не алгоритмы. Это позволяет скрыть более ценные детали реализации в отдельных библиотеках классов без шаблонов, классы внутри которых будут работать с классами шаблонов или просто использовать их для держите данные. Класс шаблона фактически будет содержать меньше кода для назначения, получения и установки данных. Остальная часть работы будет выполняться классами алгоритмов.
Я надеюсь, что это обсуждение будет полезным.
Это и возможно, пока вы знаете, какие экземпляры вам понадобятся.
добавьте следующий код в конце стека.cpp и это будет работать:
template class stack<int>;будут созданы все не шаблонные методы стека, и шаг связывания будет работать нормально.
вы можете сделать это таким образом
// xyz.h #ifndef _XYZ_ #define _XYZ_ template <typename XYZTYPE> class XYZ { //Class members declaration }; #include "xyz.cpp" #endif //xyz.cpp #ifdef _XYZ_ //Class definition goes here #endifЭто обсуждалось в Daniweb
и в часто задаваемые вопросы но с помощью ключевого слова экспорта C++.
нет, это невозможно. Не обошлось и без
exportключевое слово, которое на самом деле не существует.лучшее, что вы можете сделать, это поместить ваши реализации функций в ".ККТ" или ".ТПП " файл, и #включить .TCC файл в конце вашего .ГЭС-файл. Однако это просто косметическое средство; это все равно то же самое, что реализовать все в заголовочных файлах. Это просто цена, которую вы платите за использование шаблонов.
Я считаю, что есть две основные причины для попытки разделить шаблонный код в заголовок и cpp:
для простой элегантности. Мы все любим писать код, который можно читать, управлять и повторно использовать позже.
другое-это сокращение времени компиляции.
в настоящее время я (как всегда) кодирую программное обеспечение для моделирования в сочетании с OpenCL, и нам нравится сохранять код, чтобы его можно было запускать с использованием типов float (cl_float) или double( cl_double) по мере необходимости в зависимости по возможности HW. Прямо сейчас это делается с помощью #define REAL в начале кода, но это не очень элегантно. Изменение требуемой точности требует перекомпиляции приложения. Поскольку нет реальных типов времени выполнения, мы должны жить с этим в настоящее время. К счастью, ядра OpenCL компилируются во время выполнения, и простой sizeof(REAL) позволяет нам соответствующим образом изменять время выполнения кода ядра.
гораздо большая проблема заключается в том, что даже если приложение является модульным, когда разработка вспомогательных классов (таких как те, которые предварительно вычисляют константы моделирования) также должны быть шаблонизированы. Все эти классы появляются, по крайней мере, один раз в верхней части дерева зависимостей классов, так как окончательное моделирование класса шаблона будет иметь экземпляр одного из этих классов фабрики, что означает, что практически каждый раз, когда я делаю небольшое изменение в классе фабрики, все программное обеспечение должно быть перестроено. Это очень раздражает, но я не могу найти лучшего решения.
иногда можно скрыть большую часть реализации в файле cpp, если вы можете извлечь общую функциональность для всех параметров шаблона в класс без шаблона (возможно, тип-небезопасный). Тогда заголовок будет содержать вызовы перенаправления к этому классу. Подобный подход используется и при борьбе с проблемой" шаблонного раздувания".
Если вы знаете, с какими типами будет использоваться ваш стек, вы можете создать их эксплицитно в файле cpp и сохранить там весь соответствующий код.
также можно экспортировать их через DLL (!) но довольно сложно получить правильный синтаксис(MS-специфические комбинации __declspec (dllexport) и ключевое слово export).
мы привыкли, что в математике/геометрии lib, что шаблонный двойной/поплавок, но довольно много кода. (Я googled вокруг для него в то время не хотя этот код сегодня.)
проблема в том, что шаблон не создать реальный класс, это просто шаблон рассказывая компилятору, как создать класс. Вам нужно создать конкретный класс.
простой и естественный способ-поместить методы в файл заголовка. Но есть и другой путь.
в свой .cpp-файл, если у вас есть ссылка на каждый экземпляр шаблона и метод, который вам требуется, компилятор будет генерировать их там для использования во всем вашем проект.
новый стек.cpp:
#include <iostream> #include "stack.hpp" template <typename Type> stack<Type>::stack() { std::cerr << "Hello, stack " << this << "!" << std::endl; } template <typename Type> stack<Type>::~stack() { std::cerr << "Goodbye, stack " << this << "." << std::endl; } static void DummyFunc() { static stack<int> stack_int; // generates the constructor and destructor code // ... any other method invocations need to go here to produce the method code }
вам нужно иметь все в файле hpp. Проблема в том, что классы фактически не создаются до тех пор, пока компилятор не увидит, что они нужны какому - то другому файлу cpp, поэтому он должен иметь весь код, доступный для компиляции шаблонного класса в то время.
одна вещь, которую я обычно делаю, - это попытаться разделить мои шаблоны на общую не шаблонную часть (которая может быть разделена между cpp/hpp) и специфичную для типа часть шаблона, которая наследует не шаблонную часть класс.
только если вы
#include "stack.cppв концеstack.hpp. Я бы рекомендовал этот подход только в том случае, если реализация относительно велика, и если вы переименуете .cpp файл с другим расширением, как отличить его от обычного кода.
Это довольно старый вопрос, но я думаю, что интересно посмотреть презентацию Артур О'Двайер на cppcon 2016. Хорошее объяснение, много темы охвачены, должны смотреть.
поскольку шаблоны компилируются при необходимости, это накладывает ограничение на многофайловые проекты: реализация (определение) класса или функции шаблона должна находиться в том же файле, что и его объявление. Это означает, что мы не можем разделить интерфейс в отдельном заголовочном файле, и что мы должны включить как интерфейс, так и реализацию в любой файл, который использует шаблоны.
еще одна возможность сделать что-то вроде:
#ifndef _STACK_HPP #define _STACK_HPP template <typename Type> class stack { public: stack(); ~stack(); }; #include "stack.cpp" // Note the include. The inclusion // of stack.h in stack.cpp must be // removed to avoid a circular include. #endif
ключевое слово 'export' - это способ отделить реализацию шаблона от объявления шаблона. Это было введено в стандарт C++ без использования существующей реализации. В свое время только несколько компиляторов фактически реализовали его. Читайте подробную информацию на сообщите ему статью на экспорт
1) ЗАПОМНИТЕ основную причину разделения .h и .cpp-файлы должны скрывать реализацию класса как отдельно скомпилированный Obj-код, который может быть связан с кодом пользователя, включающим a .h класса.
2 )не шаблонные классы имеют все переменные конкретно и конкретно определенные.h и .cpp-файлы. Таким образом, компилятор будет иметь необходимую информацию обо всех типах данных, используемых в классе перед компиляцией / переводом генерация объектного / машинного кода Класс шаблона у вас нет информации о конкретном типе данных до того, как пользователь класса создаст экземпляр объекта, передающего требуемый тип данных:
TClass<int> myObj;3) только после этого экземпляра, компилятор генерировать конкретной версии класса шаблон, чтобы соответствовать переданному тип данных(ы).
4) поэтому .cpp не может быть скомпилирован отдельно, не зная конкретного типа данных пользователей. Поэтому он должен оставаться в качестве исходного кода внутри ".h " пока пользователь не укажет требуемый тип данных затем он может быть создан для определенного типа данных, а затем скомпилирован
Я работаю с Visual studio 2010, если вы хотите разделить свои файлы.h и .cpp, включите свой заголовок cpp в конце.файл H
Comments