Thursday, January 7, 2010

Compilation and linking issues for C++ template classes

The common practice in C++ is to write the class definition in a header file(.h file) and to write the class implementation in a source file(.cpp file). This reason behind this is to keep the interface separate from the implementation. This source file is compiled separately. When we want to use the class in another source file say test_stack.cpp, we need to include the header file in test_stack.cpp. We then need to compile this test_stack.cpp and link the object files to make the executable.

But if we want to follow the same practice for a template class, some linking issues arise.

Linking Issue:

Here goes the source files and header file.



//stack.h

#ifndef STACK_H
#define STACK_H

template class Stack {
T *v;
int max_size;
int top;
public:
class Underflow {};
class Overflow {};

Stack(int s);
~Stack();

void push(T);
T pop();
};

class Bad_size {};
class Bad_pop {};

#endif

//stack.cpp

#include "stack.h"

template Stack::Stack(int s) {
top = 0;
if (10000 < s) throw Bad_size();
max_size = s;
v = new T[s];
}

template Stack::~Stack() {
delete [] v;
}

template void Stack::push(T c) {
if (top == max_size) throw Overflow();
v[top++] = c;
}

template T Stack::pop() {
if (top == 0) throw Underflow();
return v[--top];
}

// test_stack.cpp
#include "stack.h"
#include
#include
#include

Stack sc(10);
Stack< std::complex > scplx(10);
Stack< std::list > sli(10);

void f() {
sc.push('a');
sc.push('b');
sc.push('c');
if (sc.pop() != 'c') throw Bad_pop();

scplx.push(std::complex(1, 2));
scplx.push(std::complex(2, 3));
scplx.push(std::complex(3, 4));

if (scplx.pop() != std::complex(3, 4)) throw Bad_pop();

std::cout << "stack of characters, sc: " << sc.pop() << ' ' << sc.pop() << '\n';
std::cout << "stack of complex numbers, scplx: " << scplx.pop() << ' ' << scplx.pop() << '\n';
}

int main() {
f();
return 0;
}



When you try to link the object files created from compiling source files, some linking problems arise:



$ g++ -c stack.cpp
$ g++ -c test_stack.cpp
$ g++ -o test_stack stack.o test_stack.o
test_stack.o: In function `__static_initialization_and_destruction_0(int, int)':
test_stack.cpp:(.text+0x56): undefined reference to `Stack::Stack(int)'
test_stack.cpp:(.text+0x5b): undefined reference to `Stack::~Stack()'
test_stack.cpp:(.text+0x87): undefined reference to `Stack >::Stack(int)'
test_stack.cpp:(.text+0x8c): undefined reference to `Stack >::~Stack()'
test_stack.cpp:(.text+0xb8): undefined reference to `Stack >
-----
------
------
collect2: ld returned 1 exit status



Reason

When the compiler encounters a declaration of a Stack object of some specific type, e.g., int , it must have access to the template implementation source. Otherwise, it will have no idea how to construct the Stack member functions. And, if you have put the implementation in a source (stack.cpp) file the compiler will not be able to find it when it is trying to compile the client source file test_stack.cpp. And, includeing the header file (stack.h) will not be sufficient at that time. That only tells the compiler how to allocate for the object data and how to build the calls to the member functions, not how to build the member functions. And again, the compiler won't complain. It will assume that these functions are provided elsewhere, and leave it to the linker to find them. So, when it's time to link, you will get "unresolved references" to any of the class member functions that are not defined "inline" in the class definition.

Solution

There are different methods to solve this problem. You can select from any of the methods below depending on which is suitable for your application design:

Way 1

You can create an object of a template class in the same source file where it is implemented
(stack.cpp). So, there is no need to link the object creation code with its actual implementation in some other file. This will cause the compiler to compile these particular types so the associated class member functions will be available at link time. Here goes the changes in stack.cpp:




#include "stack.h"

template Stack::Stack(int s) {
top = 0;
if (10000 < s) throw Bad_size();
max_size = s;
v = new T[s];
}

template Stack::~Stack() {
delete [] v;
}

template void Stack::push(T c) {
if (top == max_size) throw Overflow();
v[top++] = c;
}

template T Stack::pop() {
if (top == 0) throw Underflow();
return v[--top];
}

// No need to call this temporaryFunction() function,
// it's just to avoid link error.
void temporaryFunction ()
{
// you need to use those functions here which will be called from test_stack.cpp
Stack sc(10);
sc.push('a');
sc.pop();
}



The temporary function in "stack.cpp" will solve the link error. No need to call this function because it's global.

Way 2

You can #include the source file that implements your template class stack.cpp in your test_stack.cpp source file



#include "stack.h"
#include "stack.cpp"
#include
#include
#include

Stack sc(10);
Stack< std::complex > scplx(10);
Stack< std::list > sli(10);

void f() {
sc.push('a');
sc.push('b');
sc.push('c');
if (sc.pop() != 'c') throw Bad_pop();

scplx.push(std::complex(1, 2));
scplx.push(std::complex(2, 3));
scplx.push(std::complex(3, 4));

if (scplx.pop() != std::complex(3, 4)) throw Bad_pop();

std::cout << "stack of characters, sc: " << sc.pop() << ' ' << sc.pop() << '\n';
std::cout << "stack of complex numbers, scplx: " << scplx.pop() << ' ' << scplx.pop() << '\n';
}

int main() {
f();
return 0;
}



In this case you dont need to compile stack.cpp. You need to compile and link only test_stack.cpp

Way 3

You can #include the source file that implements your template class (stack.cpp) in your header file that defines the template class (stack.h).



#ifndef STACK_H
#define STACK_H

template class Stack {
T *v;
int max_size;
int top;
public:
class Underflow {};
class Overflow {};

Stack(int s);
~Stack();

void push(T);
T pop();
};

class Bad_size {};
class Bad_pop {};

#include "stack.cpp"

#endif



In this case you dont need to compile stack.cpp. You need to compile and link only test_stack.cpp

Way 4

You need to make the class functions inline i.e. implement those inside stack.h and remove stack.cpp. In this case you need to compile and link only test_stack.cpp

2 comments:

Junal on the run said...

Babu bhai,

Your blog looks completely technical. Ai posta ta mathar upor dia gese...C'r coding onek din pore dekhlum :P

Anyways, why don't you use WP? WP is best for SEO and other stuff, and perfect for blogging...

Thanks and keep writing :)

Omar Faruk said...

Babu,

Why don't you use wordpress theme for blogging site ?

Omar Faruk