Появилась желание написать шаблон класса, но когда я описал класс в файле .h, а реализовал методу в - .cpp, моя программа отказалась компилироваться. В поисках истины я решил скинуть всё в один файл main.cpp: и класс и его реализацию. И на моё удивление программа успешно откомпилировалась! С этого момента я начал поиски ответа на этот вопрос...
И ответ был найден. Признаюсь, я был весьма удивлён такому поведению шаблонов.
Я понял, что ошибался, когда считал шаблоны классами, когда в действительности они просто... "шаблоны".
Ниже я привожу свободный перевод статьи
Why can't I separate the definition of my templates class from its declaration and put it inside a .cpp file?
__________________________________________________________________________
Почему я не могу отделить описание моего шаблона класса от его объявления и поместить его(описание) в отдельный .cpp файл?
Если вы просто хотите решить эту проблему, обратитесь к статьям [35.13] и [35.14].Если вы хотите понять, почему это обстоит именно так и не иначе, то следует принять следущие факты:
- Шаблон - это не класс или функция. Шаблон - это "образец", который используется компилятором для генерации класса или функции.
- Чтобы компилятор сгенерировал код, он должен видеть как описание шаблона (не только его объявление), так и тип, который используется для заполнения шаблона. Например, если вы попытаетесь использовать Foo<int>, то компилятор должен видеть как шаблон Foo, так и ваше обращение Foo<int>.
- Ваш компилятор, вероятно, не запоминает детали одного cpp-файла, пока компилирует другой cpp-файл. Это возможно, но большинство компиляторов так не делают, и т.к. вы читаете эту инструкцию, ваш компилятор, очевидно, не запоминает этого. Кстати, это называется "модель раздельной компиляции" ("separate compilation model").
template<typename T> class Foo { public: Foo(); void someMethod(T x); private: T x; };с соответствующим описание методов:
template<typename T> Foo<T>::Foo() { ... } template<typename T> void Foo<T>::someMethod(T x) { ... }Теперь предположим, что вы имеете некоторый код в Bar.cpp, который использует Foo<int>:
// Bar.cpp void blah_blah_blah() { ... Foo<int> f; f.someMethod(5); ... }Очевидно, что когда кто-то использует данный шаблон, это приводит к обращению описания конструктора класса и к описанию метода someMethod(), а также к строке Foo<int>, где указан тип T (в данном случае int).
Но если мы расположим описание конструктора и метода someMethod() в файл Foo.cpp, то во время компилирования Foo.cpp компилятор увидит шаблон класса, а во время компилирования Bar.cpp - увидит Foo<int>. Но никогда не будет момента, когда компилятор сможет одновременно увидеть как шаблона класса, так и вызов Foo<int>.
Исходя из правила #2 выше, будет невозможно сгенерировать код для метода Foo<int>::someMethod().
__________________________________________________________________________
Решение
В качестве решения я выбрал следующую модель организации файлов для шаблонов класса:// MyClass-impl.cpp // Описание класса template <class T> MyClass<T>::MyClass() { x = 0; }
// MyClass.cpp // Объявление класса template <class T> class MyClass { public: MyClass(); T x; }; #include "MyClass-impl.cpp"
// MyClass.h // Заголовочный файл #ifndef MYCLASS_H #define MYCLASS_H #include "MyClass.cpp" #endif // MYCLASS_H
// main.cpp #include <iostream> using namespace std; #include "MyClass.h" int main() { MyClass<int> a; a.x = 10; cout << a.x; return 0; }
Приведу ещё пример кода шаблона из этого же раздела под-статьи [35.20] Can the previous problem hurt me silently? Is it possible that the compiler will silently generate the wrong code?
Если вы не знакомы с шаблонами, но умеете писать классы, этот пример вас повергнет в лёгкий шок )
class Xyz { ... }; //< global ("namespace scope") type void f() { } //< global ("namespace scope") function template<typename T> class B { public: class Xyz { ... }; //< type nested in class B<T> void f() { } //< member of class B<T> }; template<typename T> class D : public B<T> { public: void g() { Xyz x; //< suprise: you get the global Xyz!! f(); //< suprise: you get the global f!! } };Шаблоны имеют другое поведение в отличии от классов.
Не понятно, зачем делать три файла для задании шаблона (.h, .cpp, -impl.cpp), когда можно обойтись двумя (.h, .cpp)?
ОтветитьУдалитьВ примерах по ссылкам impl.h нужен был лишь в случае невозможности редактирования .cpp и содержал явные инстанцирования шаблонов, у вас же h вообще не содержит полезной информации, сpp содержит декларацию, что уже странно, и потом пришлось добавлять impl.cpp уже с реализацией (который место б в cpp). Мне не очевидно - зачем такое решение?