How to have a class contain different class types and call the members of those objects held?

147 views Asked by At

In my program, I find a situation where I want a UserClass that

  1. is a container that can hold heterogenous types
  2. can call member functions of the objects held.

The set of types that are held is limited and known at compile time. In fact, all of the types are just different template specializations.

This code illustrates the situation:

class ArrayBase { /*etc.*/ };  // definition not changeable

template <class T>
class TypedArray : public ArrayBase  // definition not changeable
{
 /*more functionality needed by SpecializedArray.*/
}

template<class T>
class SpecializedArray : public TypedArray<T>
{
public:
 void newFunctionalityMember() { /*etc.*/ };
};

class UserClass
{
    addArray( arrayElmentTypeEnum_t t)
    {
        switch(t) {
        case float_id:
            _elementArrays.push_back( new SpecializedArray<float>() );
            break;
        case double_id:
            _elementArrays.push_back( new SpecializedArray<double>() );
            break;
        default:
            break;  
    }

    void doSomethingWithASpecializedArray(int num)
    {
       // using _elementArrays[num], call the correct newFunctionalityMember()

    }

    private:
        std::vetor<storagePtr_t> _elementArrays;
}

Without some pleading, I don't have flexibility to change ArrayBase or TypedArray. I have left storagePtr_t type undefined, as what type it should be is a key part of my question.

I can think of one solution, illustrated below. But what a pain! This is a lot of code to have anywhere in UserClass that I need to access the element members. Is there a better way?

boost library is fair game.

my technique:

storagePtr_t is ArrayBase*, arrayElmentTypeEnum_t would be a std::type_info*

UserClass::doSomethingWithASpecializedArray(int num)
{
// these two both uniquely identified by num.
storagePtr_t * bptr = _elementArrays[num];
arrayElmentTypeEnum_t typekey = ...

    if        (typekey == &typeid(SpecializedArray<float>) ) {
        D<float> * dptr = static_cast<SpecializedArray<float>*>(bptr);
        dptr->newFunctionalityMember();
    } else if (typekey == &typeid(SpecializedArray<double>) ) {
        D<float> * dptr = static_cast<SpecializedArray<double>*>(bptr);
        dptr->newFunctionalityMember();
    } else if (typekey == &typeid(SpecializedArray<int>) ) {
        D<float> * dptr = static_cast<SpecializedArray<int>*>(bptr);
        dptr->newFunctionalityMember();
    }
} 
1

There are 1 answers

2
DRH On BEST ANSWER

You could introduce a new base class that defines the interface you want to invoke:

class SpecializedArrayBase {
public:
    virtual ~SpecializedArrayBase() {}
    virtual void newFunctionalityMember() = 0;
};

and then derive your array type from this base:

template<class T>
class SpecializedArray : public TypedArray<T>, public SpecializedArrayBase
{
public:
    void newFunctionalityMember() { /*etc.*/ };
};

You can then store objects in your vector by the new base type SpecializedArrayBase:

typedef SpecializedArrayBase* storagePtr_t; 

Since the base class defines the function you want to invoke, you can avoid all of the casting and invoke it through the base pointer:

void doSomethingWithASpecializedArray(int num)
{
   for (size_t i = 0; i < _elementArrays.size(); ++i) {
       _elementArrays[i]->newFunctionalityMember();
   }
}