heterogenous container class

Discussion in 'C++' started by A, Jan 12, 2011.

  1. A

    A Guest

    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, Jan 12, 2011
    #1
    1. Advertising

  2. A

    A Guest

    > Use a variant.

    can you elaborate that a bit?
     
    A, Jan 12, 2011
    #2
    1. Advertising

  3. A

    A Guest

    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
     
    A, Jan 12, 2011
    #3
  4. A wrote:
    > 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
     
    Ulrich Eckhardt, Jan 12, 2011
    #4
  5. A

    Goran Guest

    On Jan 12, 1:54 am, "A" <> wrote:
    > 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.
     
    Goran, Jan 12, 2011
    #5
  6. A

    Paul Guest

    "A" <> wrote in message news:igiu59$ab2$...
    > 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.
     
    Paul, Jan 12, 2011
    #6
  7. A

    Paul Guest

    "Paul" <> wrote in message
    news:X2fXo.74859$2...
    >

    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;
    };
     
    Paul, Jan 12, 2011
    #7
  8. On 12 Jan., 01:54, "A" <> wrote:
    > 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
     
    Stuart Redmann, Jan 12, 2011
    #8
  9. Stuart Redmann wrote:
    > On 12 Jan., 01:54, "A" <> wrote:
    >> 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
     
    Ulrich Eckhardt, Jan 12, 2011
    #9
  10. A

    A Guest

    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?
     
    A, Jan 13, 2011
    #10
  11. On Jan 13, 4:47 am, "A" <> wrote:
    > 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.
    >

    <snip>
    > 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
     
    Bart van Ingen Schenau, Jan 13, 2011
    #11
  12. > 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
     
    Ulrich Eckhardt, Jan 13, 2011
    #12
  13. A

    A Guest

    thank you all for your kind answers!
     
    A, Jan 14, 2011
    #13
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Vivi Orunitia
    Replies:
    11
    Views:
    4,530
    Martijn Lievaart
    Feb 4, 2004
  2. Maitre Bart
    Replies:
    2
    Views:
    539
    Maitre Bart
    Feb 11, 2004
  3. Vikas
    Replies:
    2
    Views:
    400
    David Hilsee
    Sep 11, 2004
  4. Steven T. Hatton
    Replies:
    4
    Views:
    3,962
    Rob Williscroft
    Dec 5, 2004
  5. GalenTX

    Bitfields in a heterogenous environment

    GalenTX, Feb 4, 2005, in forum: C Programming
    Replies:
    6
    Views:
    335
    GalenTX
    Feb 6, 2005
Loading...

Share This Page