A Generic C++ 11 Column-based Data Table – A full template member method specialization & inheritance use case

c++-pacman

It’s been a while since i wrote about a programming related post, right?


Well today, I am going to show you how to use templates in specializations in order to support different implementations of a member function (by data type) & hide the underlying specialization data type from the owner of the templated resources by using (unique) pointers to the resources base class.

Hmm, all this sound a bit vague, right? Well, consider the following use case!

1. Problem
Make a Data Table class that accepts & stores an arbitrary number of vectors of integers/doubles/wide character strings and is able to access each vector independently at any given moment.Each vector could be viewed as a Data Column, where each data cell in the column has the same data type as the rest cells of the given column. The Data Table class has the additional property that the last column is somewhat special and accepts an old school C style struct (the Object struct) that stores either a column of strings, integers or doubles into our table.

#include <string>
typedef struct Object{
	enum ObjectType{
	    INT,
	    DOUBLE,
	    STRING,
	    ALL
	};
	ObjectType t;
    std::wstring str;
    int i;
    double d;
} Object;


A representation of a Data Table instance could be like the following:

+-----+-------------------------------------------------------+---------+------------+---------------+-----------------+---------------+
| id  | name                                                  | city_id | country_id | creation_date | longitude       | latitude      |
+-----+-------------------------------------------------------+---------+------------+---------------+-----------------+---------------+
|   1 | download festival                                     |     345 |        826 |    1486116627 |  -1.37659071746 | 52.8253416376 |
|   2 | roadburn festival                                     |     249 |        528 |    1486116627 |         5.09283 |      51.55774 |
|  89 | crossing border festival                              |    1222 |        528 |    1486125508 |       4.3161918 |    52.0806249 |
| 174 | sweden rock festival                                  |    1281 |        752 |    1486125656 |              -1 |            -1 |
| 203 | leeds festival                                        |     283 |        826 |    1486125855 |  -1.38795856125 | 53.8692024936 |
| 204 | reading festival                                      |     187 |        840 |    1486125855 | -0.991504837082 | 51.4644779002 |
| 212 | i'll be your mirror - all tomorrow's parties festival |     181 |        826 |    1486585624 | -0.131165437732 | 51.5936679342 |
| 236 | bloodstock open air festival, catton hall             |    1286 |        826 |    1486585635 |  -1.69612337663 | 52.7419809397 |
| 284 | bråvalla festivalen - site office                     |    1297 |        752 |    1486672011 |        16.12269 |      58.60874 |
+-----+-------------------------------------------------------+---------+------------+---------------+-----------------+---------------+


Hmmm, the Data Table & its Data Columns seem like a MYSQL table isn’t it ? 🙂 The first column (id) along country_id, city_id & creation_date is of integer type, the name Data Column is of type wide string and the longitude & latitude have double as their data type.

2. Implementation
We will start with the Table class. As mentioned in the Problem definition section, our Table should store initially any number of Columns of one specific data type plus one additional special Column, the Value Column. These requirements are implemented by keeping a vector of unique pointers to Columns (Table::m_Columns class member variable) & a unique pointer to store the Value Column (Table::m_Value class member variable) respectively. Why am i using unique pointers? Well, the Table class is the owner of the Columns & i want all Column resources to be freed after the Table’s lifetime expires (Table::~Table() is called) at some point.

//Table.h
#include "DataColumnFactory.h"
#include <assert.h>

class Table{
    using ColumnRsrc = std::unique_ptr<Column>;

    std::vector<ColumnRsrc> m_Columns;
    ColumnRsrc m_Value;
    std::wstring m_Name;

    public:
    Table(const std::wstring& name):m_Name(name){}
    ~Table(){}

    template <typename T>
    void add(std::vector<T> t) {
        auto column = DataColumnFactory::getColumn<T>();
        column.get()->add(t);
        m_Columns.push_back(std::move(column) );
    }

    void addValue(Object* o){
        if(!o) return;
        if(!m_Value.get() ){
            m_Value = DataColumnFactory::getColumnByType(o);
        }            
        assert(m_Value.get());
        m_Value.get()->add(o);
    }

    void dump(){
        dumpTableGeneralInfo();

        int i = 0;
    	for(auto &column : m_Columns){
            std::wcout << "Column #" << ++i << std::endl;
    	    column.get()->dump();
    	}

        if(m_Value.get()){
            std::wcout << "Column #" << ++i << std::endl;
            m_Value.get()->dump();
        }
    }

private:
    void dumpTableGeneralInfo(){
        //...implementation skipped
    }
};


The Table class provides two methods for inserting data, the first add() method allows the user to insert Column data which will be stored in the m_Columns member method & the second add() method that accepts as argument an Object type, allowing us to insert data to the Table’s special Value Column. Both methods are using the DataColumnFactory class in order to allocate a new DataColumn instance. The first add() method’s implementation gets a template argument T that is passed to the DataColumnFactory, indicating the type of data that the newly allocated DataColumn will store. The second overload accepts as argument an Object* that is then passed to the DataColumnFactory. The DataCloneFactory knows the Object instance’s internal representation, thus it allocates & returns to our Table instance a DataColumn with type that is dependent to the Object instance’s storage type (INTEGER, STRING or DOUBLE).

//DataCloneFactory.h
#include "Column.h"

class DataColumnFactory{
public:
    template<typename T>
    static std::unique_ptr<DataColumn<T>> getColumn(){
         std::unique_ptr<DataColumn<T>> column(new DataColumn<T>);
         return std::move(column);
    }

    static std::unique_ptr<Column> getColumnByType(Object* o){
        if(!o) return nullptr;
        switch(o->t){
            case Object::ObjectType::INT:{
                return std::move ( DataColumnFactory::getColumn<int>() );
            }
            case Object::ObjectType::DOUBLE:{
                return std::move ( DataColumnFactory::getColumn<double>() );
            }
            case Object::ObjectType::STRING:{
                return std::move ( DataColumnFactory::getColumn<std::wstring>() );
            }
            default: break;
        }
        return nullptr;
    }
};


The DataCloneFactory returns a (moved) DataColumn unique pointer to the Table; as we said, the Table instance is the owner of the columns. The factory contains two static methods that are responsible for allocating the DataColumns. The first templated method DataColumnFactory::getColumn() accepts as template argument T, that is the argument passed through Table::add(std::vector), representing the new Data Column’s data type. getColumn() allocates & returns a unique pointer to the newly allocated DataColumn to the Table instance. The second static method, DataColumnFactory::getColumnByType(Object*) allocates & returns a unique pointer to a new DataColumn, depending on the Object* argument’s type value. This static method returns a unique pointer to a Column instance, the base class of all DataColumns. In both cases, after calling a factory method, the Table instance stores the newly created DataColumn/Column objects to m_Columns & m_Value respectively.

3. Polymorphism
Keep in mind that m_Columns & m_Value are actually of Column (base class)data type, not of type DataColumn (derived class from Column), allowing us to use the columns polymorphically, hiding away from the Table instance their underlying data types. Is it a std::wstring, a double, an integer Column, who knows? Let’s call a member function on them & see how they behave!

//Column.h
#include 
#include 
#include 
#include "Object.h"

class Column{
public:
    virtual ~Column(){}
    virtual void add(Object *) = 0;
    virtual void dump() = 0;
    virtual int size() = 0;
};

template <class T>
class DataColumn : public Column{
    std::vector<T> values;
    public:
    	DataColumn():values({}){}
    	virtual ~DataColumn(){}
    	virtual void add(Object*) override;

    	void add(std::vector<T> v){
	    for(int i = 0;i<v.size();i++){
                values.push_back(v[i]);
	    }
    	}

    	virtual void dump() override{
            for(auto &k : values){
    	        std::wcout << "* " << k << std::endl;
    	    }
            std::wcout << std::endl;
    	}

        virtual int size() override {return values.size();}
};

template<>
void DataColumn<int>::add(Object* o){
    values.push_back(o->i);
}

template<>
void DataColumn<std::wstring>::add(Object* o){
    values.push_back(o->str);	
}

template<>
void DataColumn<double>::add(Object* o){
	 values.push_back(o->d);
}


The Column interface provides 3 methods: add(Object*), dump() & size(). All derived classes need to provide implementations about how to add an Object* to a column, how to print the contents of a Column instance and how to return the amount of Column items (of the same data type) that are contained in the Column. The Column class is an interface & it does not store any state for the Column itself, it just represents what a generic Column should look like & what/which minimum operations should be permitted in order to allow a derived class to become Column instance.

4. Template method class specialization
The DataColumn class that extends the Column interface is a template class that takes as template argument T the data type of the elements that will store. The class contains a vector as private member, that is where the Column elements will be stored. Except the three virtual methods that were inherited from the Column interface, the DataColumn class provides also the method add(std::vector), allowing us to store the Column elements to our vector. This method is used  for storing simple Columns by the Table class. In order to store data retrieved from an Object*, three methods with template specialization are offered. Each method knows which member of the Object* to access in order to get its value from the struct & store it in its vector, depending on the Data Column’s template specialization. Thus, the DataColumn<double>::add(Object*) knows that the d struct member contains the double value and that one needs to be accessed in order to retrieve & store the value to its std::vector values. DataColumn<std::wstring>::add(Object*) in the same way knows that the str member needs to be accessed in order to retrieve & store the wstring value to its std::vector values. Finally, the same also applies for DataColumn<int>::add(Object*) which accesses the member i. By providing full template specialization implementations for each supported data type, we allow the compiler to know at compile time which function to call, allowing us to instead of writing different full implementations of the derived classes DataColumn<std::wstring>, DataColumn<double>, DataColumn<int> to just provide different method implementation depending on template specialization type of the DataColumn, keeping things generic & compact as much as possible.

5. Client Code
After having explained the whole story, let’s see how we could use our Table class in order to store some data.

//templated_inheritance.cpp
#include "Table.h"

int main(){
    //Table with wstring Value Column
    Table stringColumn(L"WStringValueColumn");
    stringColumn.add<int>({1,2,3,4});
    stringColumn.add<double>({1.1,2.2,3.1,4.1});
    stringColumn.add<std::wstring>({L"Tzertzelos Blog", L"serving",L"the underground",L"since 2013!!"});

    Object o;
    o.t = Object::ObjectType::STRING;
    o.str = L"Cult of Luna";
    stringColumn.addValue(&o);
    stringColumn.dump();

    //Table with double Value Column
    Table numericColumn(L"DoubleValueColumn");
    numericColumn.add<int>({5,6,7,8});
    numericColumn.add<double>({1.12,2.22,3.12,4.12});
    numericColumn.add<std::wstring>({L"Tzertzelos", L"B",L"LO",L"G"});

    Object o2;
    o2.t = Object::ObjectType::DOUBLE;
    o2.d = 1.3334;
    numericColumn.addValue(&o2);

    o2.d = 1.3335;
    numericColumn.addValue(&o2);
    numericColumn.dump();


    Table nullTable(L"Null");
    nullTable.dump();
    return 0;
}


This client code instantiates 3 tables, the first table’s special Value Column has wstring data type, the second instance’s Value Column has double data type & the last table is an empty table.

6. Compile & Run
Download the code from here on your Linux box, then just type “make all” in order to compile the code. Then, the out executable will be built. Run it by typing “./out“.


The output will be:

gclkaze@gclkaze-VirtualBox:~/Desktop/Projects/C++/TemplatedInheritance$ ./out
Table WStringValueColumn => 4 X 4
Cells =>13
Column #1
* 1
* 2
* 3
* 4

Column #2
* 1.1
* 2.2
* 3.1
* 4.1

Column #3
* Tzertzelos Blog
* serving
* the underground
* since 2013!!

Column #4
* Cult of Luna

Table DoubleValueColumn => 4 X 4
Cells =>14
Column #1
* 5
* 6
* 7
* 8

Column #2
* 1.12
* 2.22
* 3.12
* 4.12

Column #3
* Tzertzelos
* B
* LO
* G

Column #4
* 1.3334
* 1.3335

Table Null => 0 X Cells =>0

7. Things learned
With the previous exercise, we got an idea about how to:

  • use class templates
  • use member method template specialization
  • use unique pointers
  • use the Factory Design Pattern
  • combine polymorphism with templates.

Enjoy & drop a comment/question/suggestion for improvement if any!
Cheers,
Kazeone

6. References

I. Generic Data Table source in GitHub

II. Template specialization

III. Factory Design Pattern

IV. std::unique_ptr

V. RAII

VI. C++ STL

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s