Sunday, 2 November 2014

C++ Templates: Separate definition and implementation

Templates in C++ often force the developer to either merge the definition of the function prototypes and the implementation in a single header file (which is awkward), or to #include the implementation file in their application (i.e., do something like #include "MyClass.cpp", which is even more awkward!). The use of extern and export has been introduced in C++11, but here I'm going to present a neat way to deal with this peculiarity through an easy example.




A good reference to start with so as to understand the root of the problem is the C++ FAQ. Briefly, for templated classes, the compiler needs to know the implementation of the functions in order to instantiate them, therefore, definition and implementation do need to go hand in hand.

The workaround is pretty simple. Write the header file as follows (File: Rectangle.h) in which we declare some methods, constructors and operators for our class:

/*
 * Rectangle.h
 *
 *  Created on: Nov 2, 2014
 *      Author: Pantelis Sopasakis
 */

#ifndef RECTANGLE_H_
#define RECTANGLE_H_

#include <iostream>

using namespace std;

template<typename T>
class Rectangle
{
 T *x, *y;
public:
 Rectangle();
 Rectangle(const Rectangle& other);
 Rectangle<T>(T x, T y);
 virtual ~Rectangle();
 T area();
 Rectangle operator+ (const Rectangle& r);
 void operator=(const Rectangle& obj);

};

#include "Rectangle.cpp"

#endif /* RECTANGLE_H_ */

and note that inside the header file we have included the implementation (File: Rectangle.cpp).

 
The cpp file reads as follows:

#include "Rectangle.h"

#ifndef RECTANGLE_IMPL__
#define RECTANGLE_IMPL__


template<typename T> T Rectangle<T>::area()
{
 return *x * *y;
}

template<typename T> Rectangle<T>::Rectangle() {
 x = new T;
 y = new T;
 *x = (T)1;
 *y = (T)1;
}

template<typename T> Rectangle<T>::Rectangle(T a, T b){
 x = new T;
 y = new T;
 *x = a;
 *y = b;
}

template<typename T> Rectangle<T>::~Rectangle() {
 if (x!=0){
  delete x;
  x = 0;
 }
 if (y != 0) {
  delete y;
  y = 0;
 }
}

template<typename T> Rectangle<T>::Rectangle(const Rectangle& other){
 this->x = new T;
 this->y = new T;
 *this->x = *other.x;
 *this->y = *other.y;
}

template<typename T> Rectangle<T> Rectangle<T>::operator+ (const Rectangle& r){
 T x_ = (*x) + *(r.x);
 T y_ = (*y) + *(r.y);
 Rectangle c(x_, y_);
 return (c);
}

template<typename T> void Rectangle<T>::operator=(const Rectangle& obj)
{
    *x = *(obj.x);
    *y = *(obj.y);
    return;
}


#endif

(the guards  RECTANGLE_IMPL_ are necessary!). So that' all! Let's see how it works in practice.


In our main file we include the header file only (which includes the implementation).

#include <iostream>
#include <iomanip>
#include "Rectangle.h"

using namespace std;

int main() {
  Rectangle<int> a(2,3);
  cout <<  setprecision(3) << "Area(a) = " << a.area() << "\n";

  Rectangle<float> *r = new Rectangle<float>(4.5, 2.5);
  cout << setprecision(3) << "Area(r) = " << r->area() << "\n";

  Rectangle<float> q(1.1,2.2);
  q = q + (*r);

  cout << setprecision(3) << "Area(q) = " << q.area() << "\n";

  cout << "OK!\n";
  return 0;
}





3 comments:

  1. And what is with a class that has a templated method?

    class A
    {
    template
    foo( B const & b );
    };

    ReplyDelete
  2. It's basically the same: you simply put the implementation of your templated member function into that cpp file.

    If you have a bunch of ordinary member functions that you prefer to put into a classic cpp file, do so. You can give the file for your template implementation another extension, say *.hpp.

    This allows to have non inline member functions.

    ReplyDelete
  3. Thank you my teacher makes us do it this way

    ReplyDelete