Templates and separate compilation

I want to write a C ++ program with separate compilation, and I wrote this:

main.cpp

#include <iostream> #include "Stack.h" using namespace std; int main(int argc,char* argv[]) { Stack<int> st; st.push(1); return 0; } 

Stack.h

 #ifndef _STACK_H #define _STACK_H template<typename T> class Stack { private: struct Node { Node* _prev; T _data; Node* _next; }; int _size; Node* _pos; public: Stack(); T pop(); void push(T const &el); int getSize() const; }; #endif 

Stack.hpp

 #include "Stack.h" #include <malloc.h> template <typename T> Stack<T>::Stack() { _size = 0; _pos = (Node*)malloc(sizeof(Node)); _pos->_prev = NULL; _pos->_next = NULL; } template <typename T> T Stack<T>::pop() { if (_size == 0) return NULL; T tmp = _pos->_data; if (_pos->_prev == NULL) free(_pos); else { _pos->_prev->_next = _pos->_next; if (_pos->_next != NULL) { _pos->_next->_prev = _pos->_prev; } free(_pos); } _size--; return tmp; } template <typename T> void Stack<T>::push(T const &el) { Node* n = (Node*)malloc(sizeof(Node)); _pos->_next = n; n->_prev = _pos; n->_data = *el; _pos = n; _size ++; } template<typename T> int Stack<T>::getSize() const {return _size;}; 

I compiled the program with g ++ and I get this error:

 ccyDhLTv.o:main.cpp:(.text+0x16): undefin ed reference to `Stack<int>::Stack()' ccyDhLTv.o:main.cpp:(.text+0x32): undefin ed reference to `Stack<int>::push(int const&)' collect2: ld returned 1 exit status 

I know that the problem is that I use templates, but I do not know how to fix it.

OS - Windows compilation line - g++ main.cpp Stack.h Stack.hpp -o main.exe

+4
source share
2 answers

Template classes must have method definitions inside the header file.

Move the code that you have in the .cpp file inside the header, or create a file called .impl or .imp , move the code there and include it in the header.

The compiler must know the definitions of methods for generating code for all specializations.

Before you ask, no, there is no way to keep the implementation out of the header.

+4
source

I would say that it will be more pragmatic to first understand how a separate compilation works for normal (untempelated) files, and then understand how the g ++ compiler does this for a template.

First, in regular files, when the header file containing only declarations # is included in the main file, the preprocessor replaces the declarations from the header and places it in the main file. Then, after the preprocessing phase is completed, the compiler makes one compilation of the pure C ++ source code contained in the .cpp files, and translates it into an object file. At this point, the compiler does not mind missing definitions (functions / classes), and object files can refer to characters that are not defined. The compiler, therefore, can compile the source code if it is well-formed.

Then, at the build stage, the compiler links several files together, and at this stage, the linker will cause an error if there are no / duplicate definitions. If the function definition is correctly present in another file, the linker continues, and the function called from the main file is successfully associated with the definition and can be used.

For templates, everything works differently. This will be an illustration to consider an example, so I choose a simple one:

consider the header file for the template array class:

array.h

 #ifndef _TEMPLATE_ARRAY_H_ #define _TEMPLATE_ARRAY_H_ template <class T> class Array { private: T *m_list; int m_length; public: Array() //default constructor { m_list = nullptr; m_length = 0; } Array(int length) { m_list = new T[length]; m_length = length; } ~Arrary() { delete[] m_list; m_list = nullptr; } //undefined functions int getLength(); T getElement(const int pos); }; 

and the corresponding array.cpp file:

 include "array.h" template <class T> array<T>::getLength() { return m_length; } template <class T> T Array<T>::getElement(const int pos) { return m_list[pos]; } 

Now consider the main file in which two instances of an array of template objects are created: one for int and the other for double.

main.cpp

 #include "array.h" #include <iostream> int main() { Array<int> int_array; Array<double> double_array; std::cout << int_array.getLength() <<"\n"; std::cout << double_array.getLength() << "\n"; } 

When this piece of code is compiled, the preprocessor first copies the template declarations from the header file to the main file, as usual. Since the main file contains Array <int> and Array <b> double objects, the compiler creates two different definitions of the Array class, one for double and int, and then creates instances of Array objects in the main.cpp file.

Note that up to this point the function definitions for the array <int> :: getLength () and Array <double> :: getLength () are still missing in the main.cpp file, but since the source code is well-formed, the compiler compiles the main file. cpp without any problems. In short, there is still no difference b / w templated compilation of an object / function and compilation not templated.

Meanwhile, the code file for array.cpp containing the template function definitions for Array <T> :: getLength () and Array <T> :: getElement () has been compiled, but by this time the compiler forgot that main.cpp requires Array <int > :: getLength () and Array <double> :: getLength () will happily compile array.cpp code without generating any instances for int and a double version of the function definition needed by main.cpp. (Remember that the compiler compiles each file separately!)

At the linking stage, static errors are triggered due to missing function definitions for int and a double version of the template function definition, which are required by the main file. In the case of declarations and definitions without a template, the programmer will definitely determine the desired function in the file, which can be associated with the file that calls the function. But in the case of templates, the linker that executes after the compilation phase cannot complete the task that the compiler must perform, that is, generate code in this case for the int and double type of the template function

There are ways around this.

After going through the whole story, you can easily conclude that all the fuss about a separate separate template compilation is associated with binding (i.e.) if all the codes are written correctly, the class and functions are declared in the header and defined in another separate file), Ways to get around this:

  • Define the class and functions in the header files themselves, and not in a separate file, so that the contents of the header file, if included in the main file, includes template definitions that call the corresponding instances of the necessary functions to define the compiler.

  • Identify the types that you know will be needed in a separate file where the template definitions are written. Then it will be directly related to function calls in the main file.

  • Another way around this is to name the .cpp file, where the definitions are written to the .inl * file (from the above, chagne array.cpp to array.inl); inl means inline and includes the .INL file from the bottom of the header file. This gives the same result as defining all the functions in the header file, but helps to slightly improve the code.

  • Another way: ie # include.cpp file with definition templates in the main file, which I personally do not prefer because of the non-standard use of #include.

0
source

Source: https://habr.com/ru/post/1395382/


All Articles