heterogenous container class

A

A

I'd like to create a heterogenous container class of various objects.
I would use std::vector for holding such objects.
Let's assume we have ClassA, ClassB objects

struct TClassA
{
int i;
TClassA() : i(0) {} // default init
TClassA(int ii) : i(ii) {} // init with value
}

struct TClassB
{
float f;
TClassB() : f(0.0) {} // default init
TClassB(float ff) : f(ff) {} // init with value
}

struct TContainer
{
TClassA ca; // holds a
TClassB cb; // holds b
int type; // what is stored, a or b

TContainer(TClassA ainit) : ca(ainit), type(0) {} // init for a
TContainer(TClassB binit) : cb(binit), type(1) {} // init for b
}

// Using the above
std::vector<TContainer> Container;
Container.push_back(TClassA(123)); // Add integer
Container.push_back(TClassB(123.456)); // Add float

//read
if (Container[0].type == 0) int i = Container[0].ca;
if (Container[0].type == 1) float f = Container[0].cb;


The above works but for larger structures it leaves unnecessary memory
footprint because each class holds init functions and container also holds
all of the redundant data.

Can anyone give some clues how to restructure the above so it can be read
and written to similarly easy like the above but without the burden of
redundant constructors in each object e.g for TClassA to hold only "int i"
and not much else...

Any ideas are welcome.
 
A

A

I think I may get rid of the struct TContainer using boost::any. From what
i've seen it is probably the best way of implementing heterogenous
containers. Maybe boost::variant could be used as alternative.

eg.

// Using the above
std::vector<boost::any> Container;
Container.push_back(TClassA(123)); // Add class with integer
Container.push_back(TClassB(123.456)); // Add class with float
 
U

Ulrich Eckhardt

A said:
I think I may get rid of the struct TContainer using boost::any. From
what i've seen it is probably the best way of implementing heterogenous
containers. Maybe boost::variant could be used as alternative.

Right, both are ways to consider. The main difference is that the overhead
of "any" is bigger, but also you can store really anything in it.

The "variant" knows the possible types and can already allocate storage
for the largest part, constructing objects as they are assigned. "any" has
to allocate according to what comes.

Generally, I'd use "variant", because they have the better interface.
Using "any" is a bit like fiddling with void pointers, you have to do lots
of type casting manually.

Good luck!

Uli
 
G

Goran

Can anyone give some clues how to restructure the above so it can be read
and written to similarly easy like the above but without the burden of
redundant constructors in each object e.g for TClassA to hold only "int i"
and not much else...

Any ideas are welcome.

I think that you need to think bigger about __operations__ your
TContainer should provide (currently it's a simple data storage). It
looks like you need some run-time polymorphism, but it's not clear
from uses you have shown. For example, in

if (Container[0].type == 0) int i = Container[0].ca;
if (Container[0].type == 1) float f = Container[0].cb;

What is really interesting is what happens with i and f later. You
should be able to extract services that TContainer should provide for
that "later" part. But that would require e.g.

std::vector<TContainer*> Container;

If you don't like that, you might use a wrapper that provides said
services, e.g.:

class TContainerHandler
{
public:
TContainerHandler(TClassBase& data) : FData(data) {}
virtual operation1() = 0;
virtual operation2() = 0;
protected:
TClassBase& FData;
};
// derive for TClassA and TClass B.

and then:

TContainerHandler& h = Container[0].type == 0 ?
TContainerHandlerA(Container[0]) : TContainerHandlerB(Container[0]);
// above needs some casting, perhaps doesn't work properly, either
Use(h);

Goran.
 
P

Paul

A said:
I'd like to create a heterogenous container class of various objects.
I would use std::vector for holding such objects.
Let's assume we have ClassA, ClassB objects

struct TClassA
{
int i;
TClassA() : i(0) {} // default init
TClassA(int ii) : i(ii) {} // init with value
}

struct TClassB
{
float f;
TClassB() : f(0.0) {} // default init
TClassB(float ff) : f(ff) {} // init with value
}

struct TContainer
{
TClassA ca; // holds a
TClassB cb; // holds b
int type; // what is stored, a or b

TContainer(TClassA ainit) : ca(ainit), type(0) {} // init for a
TContainer(TClassB binit) : cb(binit), type(1) {} // init for b
}

// Using the above
std::vector<TContainer> Container;
Container.push_back(TClassA(123)); // Add integer
Container.push_back(TClassB(123.456)); // Add float

//read
if (Container[0].type == 0) int i = Container[0].ca;
if (Container[0].type == 1) float f = Container[0].cb;


The above works but for larger structures it leaves unnecessary memory
footprint because each class holds init functions and container also holds
all of the redundant data.

Can anyone give some clues how to restructure the above so it can be read
and written to similarly easy like the above but without the burden of
redundant constructors in each object e.g for TClassA to hold only "int i"
and not much else...

Any ideas are welcome.
I did some work on this many years ago, it basically re-allocates anytime
you change the data type of an element. I don't know if this is any use as
it would require some work to incorporate vector like behavior.
Please find some example code that demonstrates the general class design I
began with:

#include <iostream>
#include <vector>
typedef unsigned int UINT;

template<class T1, class T2,class T3>
class bMixedArray{
public:
virtual void Allocate(const T1& rhs){}
virtual void Allocate(const T2& rhs){}
virtual void Allocate(const T3& rhs){}
};

template<class T1, class T2,class T3>
class bDataItem{
private:
bMixedArray<T1,T2,T3>* itsContainer;
public:
virtual ~bDataItem(){}
//bDataItem():itsContainer(0){}
bDataItem(bMixedArray<T1,T2,T3>* p):itsContainer(p){}

virtual bDataItem& IndexMe(){return *this;}

virtual bDataItem& operator=(const T1& rhs){
if(itsContainer)
itsContainer->Allocate(rhs);
return *this;}
virtual operator T1(){return 0;}

virtual bDataItem& operator=(const T2& rhs){
if(itsContainer)
itsContainer->Allocate(rhs);
return *this;}
virtual operator T2(){return 0;}

virtual bDataItem& operator=(const T3& rhs){
if(itsContainer)
itsContainer->Allocate(rhs);
return *this;}
virtual operator T3(){return 0;}
};

template<class T,class T1,class T2,class T3>
class DataItem:public bDataItem<T1, T2,T3>{
private:
T itsData;
public:
~DataItem(){}
//DataItem(){}
DataItem(bMixedArray<T1,T2,T3>* p):bDataItem<T1,T2,T3>(p){}
DataItem& operator=(const T& rhs){itsData = rhs; return *this;}
operator T(){return itsData;}
DataItem<T,T1,T2,T3>& IndexMe(){return *this;}
};



template<class T1, class T2, class T3>
class MixedArray:public bMixedArray<T1,T2,T3>{
private:
bDataItem<T1,T2,T3>** itsArray;
UINT itsSize;
UINT lastIndexed;
public:
MixedArray(UINT size):itsSize(size),lastIndexed(0){
itsArray= new bDataItem<T1,T2,T3>*[size];
for(UINT i=0; i<itsSize; i++)
itsArray = 0;
}

~MixedArray(){
for(UINT i=0; i<itsSize; i++){
delete itsArray;}
delete [] itsArray;
}

void Allocate(const T1& rhs){
delete itsArray[lastIndexed];
itsArray[lastIndexed] = new DataItem<T1,T1,T2,T3>(this);
*(itsArray[lastIndexed]) = rhs;
}
void Allocate(const T2& rhs){
delete itsArray[lastIndexed];
itsArray[lastIndexed] = new DataItem<T2,T1,T2,T3>(this);
*(itsArray[lastIndexed]) = rhs;
}
void Allocate(const T3& rhs){
delete itsArray[lastIndexed];
itsArray[lastIndexed] = new DataItem<T3,T1,T2,T3>(this);
*(itsArray[lastIndexed]) = rhs;
}

bDataItem<T1,T2,T3>& operator[](UINT i){
if(i<itsSize){
if(itsArray){
return itsArray->IndexMe();
}else{
itsArray = new bDataItem<T1,T2,T3>(this);
lastIndexed = i;
return *(itsArray);
}
}
}
};


int main(){
MixedArray<char*, int , float> myArray(100);//Template parameters define
allowed types.

myArray[0] = "hello\0";
myArray[0] = 5;
myArray[0] = 7.56f;

myArray[99] = "This is element 99\0"


std::cout<< sizeof(myArray)<<std::endl;

std::vector<int> v(100);
std::cout<< sizeof(v);


return 0;
}


This are some restrictions in this class desgin , the fact that you have a
limited amount of parameter types, 3 in this case. Also it lacked
compatability with the std lib.
I began a more advanced version which uses a different template organisation
but it is quite complicated. I willl post some code in a sep post to give
you the general idea.
 
P

Paul

Please find the incomplete and buggy code I mentioned in my previous post.
The intention of this class was that a container class would be created to
contain an array of bData pointers.
I'm basically posting this to show the different template design I found
necessary for an indefinite list of type parameters and compatability with
the STL.

#include <iostream>

template<class T1=int, class T2=char, class T3=double, class T4=bool>
class traits{
public:
struct Empty{};
template<class H,class T>struct LN{
typedef H head;
typedef T tail;
};
template<class t1=Empty,class t2=Empty,class t3=Empty,class t4=Empty>struct
List{
typedef LN<t1,LN<t2,LN<t3,LN<t4,Empty> > > > type;
};
typedef typename List<T1,T2,T3,T4>::type type_list;

template<class T>
struct size_of{
enum{value = sizeof(T)};
};

template<int I>struct get_type{
typedef Empty type;
};
template<>struct get_type<1>{
typedef T1 type;
};
template<>struct get_type<2>{
typedef T2 type;
};
template<>struct get_type<3>{
typedef T3 type;
};
template<>struct get_type<4>{
typedef T4 type;
};


template<class T, class U>struct is_same{
enum{value = 0};
};
template<class T>struct is_same<T,T>{
enum{value = 1};
};

template<typename T, typename L>struct is_member{
enum{value= traits::is_same<T, typename L::head>::value ||
traits::is_member<T, typename L::tail>::value};
};
template<typename T>struct is_member<T, typename traits::Empty>{
enum{value= false};
};

template<class T, class L>struct index{
enum{ value = is_member<T,L>::value + traits::index<T, typename
L::tail>::value };
};
template<class T>struct index<T, typename traits::Empty>{
enum{value = 0};
};
template<class L>struct index<typename L::head, L>{
enum{value = 1};
};

template<class L>
struct length{
enum{ value = not_empty<L::head>::value + length<typename L::tail>::value};
};
template<>
struct length<typename traits::Empty>{
enum{value=0};
};

template<typename L>struct not_empty{
enum{value = 1};
};
template<>struct not_empty<typename traits::Empty>{
enum{value = 0};
};

typedef typename List<int,char,unsigned,long>::type int_types;

};


//template<class T1>class bData;
//template<class T1,class T2>class Data;

template<class _traits=traits<> >
class bData{
public:
bData(){std::cout<<"bData()\n";}
bData(const bData& rhs){std::cout<<"bData(const bData&)\n";}

virtual ~bData(){std::cout<<"~bData()\n";}
virtual bData& operator=(const bData&
rhs){std::cout<<"bData::eek:perator=(const bData&)\n";return *this;}

typedef typename _traits::type_list::head t1;
typedef typename _traits::type_list::tail node2;
typedef typename node2::tail node3;
typedef typename node3::tail node4;
typedef typename node2::head t2;
typedef typename node3::head t3;
typedef typename node4::head t4;

virtual bData& operator=(const t1&){std::cout<<"bData::eek:perator=(const
t1&)\n"; return *this;}
virtual bData& operator=(const t2&){std::cout<<"bData::eek:perator=(const
t2&)\n"; return *this;}
virtual bData& operator=(const t3&){std::cout<<"bData::eek:perator=(const
t3&)\n"; return *this;}
virtual bData& operator=(const t4&){std::cout<<"bData::eek:perator=(const
t4&)\n"; return *this;}
virtual operator t1()const{std::cout<<"bData::eek:perator t1\n"; return t1();}
virtual operator t2()const{std::cout<<"bData::eek:perator t2\n"; return t2();}
virtual operator t3()const{std::cout<<"bData::eek:perator t3\n"; return t3();}
virtual operator t4()const{std::cout<<"bData::eek:perator t4\n"; return t4();}

virtual int size_of(){return 0;}
};

template<class T,class _traits=traits<> >
class Data:public bData<_traits>{
public:
Data(){std::cout<<"Data()\n";}
Data(const T& d){itsData = d; std::cout<<"Data(T&)\n";}

~Data(){std::cout<<"~Data()\n";}
Data(const Data& rhs){itsData = rhs.itsData; std::cout<<"Data(cost
Data&)\n";}

Data& operator=(const T& rhs){itsData=rhs;
std::cout<<"Data::eek:perator=(const T&)\n"; return *this;}
Data& operator=(const Data& rhs){itsData=rhs.itsData;
std::cout<<"Data::eek:perator=(const Data&)\n"; return *this;}
Data& operator=(const bData<_traits>& rhs){itsData = rhs;
std::cout<<"Data::eek:perator=(const bData&)\n"; return *this;}
operator T()const{std::cout<<"Data::eek:perator T\n"; return itsData;}

int size_of(){return _traits::size_of<T>::value;}
private:
T itsData;
};
 
S

Stuart Redmann

I'd like to create a heterogenous container class of various objects.
I would use std::vector for holding such objects.
Let's assume we have ClassA, ClassB objects

struct TClassA
    {
    int i;
    TClassA() : i(0) {}        // default init
    TClassA(int ii) : i(ii) {} // init with value
    }

struct TClassB
    {
    float f;
    TClassB() : f(0.0) {}        // default init
    TClassB(float ff) : f(ff) {} // init with value
    }

struct TContainer
    {
    TClassA ca;    // holds a
    TClassB cb;    // holds b
    int     type;  // what is stored, a or b

    TContainer(TClassA ainit) : ca(ainit), type(0) {} // init for a
    TContainer(TClassB binit) : cb(binit), type(1) {} // init for b
    }

// Using the above
std::vector<TContainer> Container;
Container.push_back(TClassA(123));        // Add integer
Container.push_back(TClassB(123.456));    // Add float

//read
if (Container[0].type == 0) int   i = Container[0].ca;
if (Container[0].type == 1) float f = Container[0].cb;

The above works but for larger structures it leaves unnecessary memory
footprint because each class holds init functions and container also holds
all of the redundant data.

Can anyone give some clues how to restructure the above so it can be read
and written to similarly easy like the above but without the burden of
redundant constructors in each object e.g for TClassA to hold only "int i"
and not much else...

Any ideas are welcome.

Why don't you just use a union?

#include <vector>

struct TClassA
{
int i;
} ;


struct TClassB
{
float f;
} ;

struct TContainer
{
union
{
TClassA ca; // holds a
TClassB cb; // holds b
} c;
int type; // what is stored, a or b


TContainer(int ainit) : type(0)
{
c.ca.i = ainit;
} // init for a
TContainer(float binit) : type(1) // init for b
{
c.cb.f = binit;
}
} ;



// Using the above
int main ()
{
std::vector<TContainer> Container;
Container.push_back(TContainer (123)); // Add integer
Container.push_back(TContainer (123.456f)); // Add float


//read
if (Container[0].type == 0) int i = Container[0].c.ca.i;
if (Container[0].type == 1) float f = Container[0].c.cb.f;
}

Regards,
Stuart
 
U

Ulrich Eckhardt

Stuart said:
I'd like to create a heterogenous container class of various objects.
I would use std::vector for holding such objects.
[...]
Why don't you just use a union?

You can only use unions on PODs.
struct TClassA
{
int i;
} ;


struct TClassB
{
float f;
} ;

struct TContainer
{
union
{
TClassA ca; // holds a
TClassB cb; // holds b
} c;


These two are PODs, so a union would work. Otherwise, to explain why a
union doesn't work, ask yourself the question who's constructor and
destructor should be called if such a union is created or destroyed.

Take a look at Boost.Variant. It has a variant template which a) takes
care of properly creating and destroying any object type and b) keeps
track of which type is currently used.

Uli
 
A

A

the reply from paul is a bit of overkill for me.

boost::variant is just fine for this purpose. I simply define all possible
classes used eg.

std::vector< boost::variant<TClassA, TClassB> > var;

var.push_back(TClassA(123));
var.push_back(TClassB(123.456f));

Why fiddling with unions and all that stuff. I also found later that variant
is good for this purpose and it can also detect some errors in compiletime
due the fact it has predefined possible classes.

reading is also quite easy:

TClassA a = boost::get<TClassA> (var);

it also gives compile error if you do
TClassB b = boost::get<TClassA> (var);

which is great...

The only thing I haven't found yet is how to check the type of
boost::variant before reading it with boost::get ... perhaps I just didn't
look hard enough... Anyone knows?
 
B

Bart van Ingen Schenau

the reply from paul is a bit of overkill for me.

I would expect so, as it looks like it is a home-grown version of
boost::variant.
boost::variant is just fine for this purpose. I simply define all possible
classes used eg.
The only thing I haven't found yet is how to check the type of
boost::variant before reading it with boost::get ... perhaps I just didn't
look hard enough...  Anyone knows?

You can retrieve that information with boost::variant<>::which().

Bart v Ingen Schenau
 
U

Ulrich Eckhardt

The only thing I haven't found yet is how to check the type of
boost::variant before reading it with boost::get ...

I'd say you really didn't. Anyhow, there are three ways to check the type:

1. As mentioned, use which().

2. Use boost::get<T>.
If T is a reference type or a pointer type, you will get feedback whether
it fits similarly to dynamic_cast.

3. Use a visitor.

The visitor pattern allows you to supply a functor and the according
function there is called.
perhaps I just didn't look hard enough...

Maybe. ;) You should be able to find all docs online at Boost's, including
details to the above three variants.

Uli
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,962
Messages
2,570,134
Members
46,692
Latest member
JenniferTi

Latest Threads

Top