Link to home
Create AccountLog in
Avatar of tampnic
tampnicFlag for United Kingdom of Great Britain and Northern Ireland

asked on

STL containers of templated classes

Please refer to question https://www.experts-exchange.com/questions/27433431/Table-with-name-and-an-attribute-list.html?anchorAnswerId=37090245#a37090245 for background information.

typedef std::string Column1; // Column 1 is a string value
typedef std::pair<std::string,std::string> NVpair; // one name-value pair
typedef std::vector<NVpair> Column2; // Column 2 is a sequence of name-value pairs
typedef std::pair<Column1, Column2> Row; // one row of the table
typedef std::vector<Row> sequentialTable; // the table is a sequence of rows

Open in new window


is the proposed solution for the simple case where all data is strings.

As a little academic exercise for myself I knocked up a program in MS Visual Studio which uses the data structure above as a basis, but handles different types of values in column 2 using a template class. The vector<> of templated objects introduced most of the complexity. Full working source code is attached.

I found it simple to populate the table but iterating through it without type information got a bit complicated. The code works but I thought there might be a better way to handle templating the "multiple types" scenario?

Cheers,
   Chris


 
// testpad.cpp : Defines the entry point for the console application.
//
#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

#include <iostream>
#include <iomanip>
#include <utility>
#include <vector>

typedef std::string Column1; // Column 1 is a string value

// base class of template class NVpair. 
// STL containers cannot contain templated types
// so use a wrapper and an abstract base class
class basic_NVpair 
{
protected:
	virtual ~basic_NVpair(){};
public:
	virtual const std::type_info& GetType() = 0; 
};

// This class is the template for Column2 name-value pairs
template <class  T1> class NVpair : public basic_NVpair
{
public:
	std::pair<std::string,T1> data;
	const std::type_info& GetType(){ return typeid(this); }
};

// Column 2 is a sequence of pointers to name-value pairs
// We use a base class pointer because vectors can't contain templated types
typedef std::vector<basic_NVpair*> Column2; 

// a row is a pair of pointers to Column1 and Column2 data
typedef std::pair<Column1*, Column2*> Row; 

// a table is a sequence of pointers to row data
typedef std::vector<Row*> sequentialTable; 

class Table 
{
public:
	Table(){};
	~Table(){};
	void InsertRow(Row* prow)
	{
		m_Rows.push_back(prow);
	}
	void Print (std::ostream& os)
	{
		// iterate through the table over each row
		sequentialTable::iterator it;
		for ( it = m_Rows.begin(); it < m_Rows.end(); ++it) {
			// print column 1
			os << std::setw(15) << ((*it)->first)->c_str() << "||";
			// print column 2 name-value pairs
			Column2::iterator it2;
			for ( it2 = (*it)->second->begin(); it2 < (*it)->second->end(); ++it2) {
				// we have to decode the correct type to print the value
				// so try dynamic casting to the various types
				if ( NVpair<int>* nvp = dynamic_cast<NVpair<int>*>(*it2) ) {
					os << nvp->data.first.c_str() << nvp->data.second;
				} else if ( NVpair<char>* nvp = dynamic_cast<NVpair<char>*>(*it2) ) {
					os << nvp->data.first.c_str() << nvp->data.second;
				} else if ( NVpair<std::string>* nvp = dynamic_cast<NVpair<std::string>*>(*it2) ) {
					os << nvp->data.first.c_str() << nvp->data.second.c_str();
				} else {
					os << "\n***ERROR***: Type not handled: " << (*it2)->GetType().name() ;
				}
				// some space between data items
				os << "  ";
			}		
			// next line
			os << std::endl;
		}
	}
private:
	sequentialTable m_Rows;
};

// the application entry point
int _tmain(int argc, _TCHAR* argv[])
{

	Column1 myColumn1;
	Column2 myColumn2;
	Table myTable;
	Row	myRow;

	// first column text 
	myColumn1 = "Column 1 txt";

	// create some data of varying types
	NVpair<int>	myNVpair1;
	myNVpair1.data = std::make_pair("1st (int) = "	,34);
	NVpair<char> myNVpair2;
	myNVpair2.data = std::make_pair("2nd (char) = "	,'x');
	NVpair<std::string> myNVpair3;
	myNVpair3.data = std::make_pair("3rd (string) = ",std::string("a STL string"));
	NVpair<double> myNVpair4;
	myNVpair4.data = std::make_pair("4th (double) = " ,7.934);

	// point Column2 to the data
	myColumn2.push_back(&myNVpair1);
	myColumn2.push_back(&myNVpair2);
	myColumn2.push_back(&myNVpair3);
	myColumn2.push_back(&myNVpair4);

	// point the Row to the data
	myRow.first = &myColumn1;
	myRow.second = &myColumn2;

	// insert pointers to the data into the table
	myTable.InsertRow(&myRow);

	// print the data
	myTable.Print(std::cout);

	return 0;
}

Open in new window

SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Avatar of tampnic

ASKER

Thanks for the input Ricky, no need to code up a solution but the offer is appreciated. I was just looking for suggestions for different techniques I could apply to the problem.

This is a learning exercise so its better if I implement it myself and appreciate the issues from my own trial and error. I'll let you know how I am getting on and maybe get some clarification in a couple of days.

Cheers,
  Chris
No worries, just let me know if/when you need any help.
ASKER CERTIFIED SOLUTION
Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
Avatar of tampnic

ASKER

Thanks sara.

I have been trying out your solution by profiling a test scenario where I load 1000 rows then Table::print() them. I compared it to my own solution. Most of the applications time is spent putting characters on the screen so I modified your output format to match mine exactly.

My solution is faster at loading the data and your solution is faster at printing. Overall my original code was approx 2% quicker - so practically no difference!  When I increased the test to 10,000 rows, approx 1.5% difference - the run times are starting to converge. I was worried that the "dynamic_cast" element of my print() function would be a performance killer but it seems to stand up at the moment!

I was going to change the test to load data and then iterate through the table, adding one to every integer I found in Column2. The aim was to iterate through the table and perform a numerical operation but remove screen outputting from the comparison. It was a trivial change in my code, but the only way I could see of doing it in your solution was to create a + operator on the Variant class and do something if a dynamic_cast of the BaseData* pdb to <int> was successful. That puts me in a circle - I want to compare solutions which avoid dynamic_cast. Is that the way you would have approached the problem? I'm starting to think I have a mental block and everything can be solved with a dynamic_cast :-)

I will be looking at the Visitor pattern suggested by Ricky in the next couple of days and see how performance compares too. I had considered using boost::variant when I was writing the original code but haven't investigated that path yet.

I do understand that performance is not everything and that readabillity and maintainability are also big factors in producing good code. I should have stated in my question  -  I have a feeeling the code might perform poorly in comparison to other techniques due to the dynamic_cast and can be made better.

Cheers,
   Chris
One advantage the visitor pattern has is that it is statically type safe (ie the compiler will tell you at compile type if the types you are trying to use are not handled). I would also expect it to be pretty fast since all the hard work is done at compile time.

On the negative side, the pattern can be a little harder to follow and implement and you are restricted to placing all your type specific logic in visitors.

It's worth trying to get your head around this pattern though as once you understand how it works it is a great little pattern to use to perform safe static to dynamic type (down-casts) conversion without any runtime code (as I said before, it's all figured out at compile type via the template compiler).
for measuring performance i would suppress any output on screen and on file. those operations are so much slower than all rest such that you won't get differences caused by non-output functionality. you could use a stringstream that was printed out after measure but even stream operations may eat more time than that you want to measure. of course all measurements should be done in release mode.

i also would replace the pair and make_pair by a struct and normal construction. generally, the dynamic cast is one access into the virtual access table what should cost less or equal time than a virtual call. the variant class doesn't cast same as the visitor pattern and the only pointer used is mandatory because of virtual usage. if my approach needs more time than yours it is due to copy semantic. all objects were stored as copies in the containers (while your solution stores pointers) and each push_back therefore needs to create two objects. if you need best performance you could push_back an empty entry, get a reference of that entry with vector::back and then assign the real object.

Sara



Avatar of tampnic

ASKER

Sorry I haven't been back to this yet sara and ricky, I had to drop it due to a pressing issue arising. One of the the wife's business servers got hacked and I have been conducting a digital witch-hunt to clean her place up and secure the site (she doesn't use me for IT but I think we have agreed I will get more involved in decisions in the future!) Hopefully I will get back to this in a couple of days.

Thanks for your patience.
    Chris
This question has been classified as abandoned and is closed as part of the Cleanup Program. See the recommendation for more details.