Creating/managing a heterogenous container(c++)

668 Views Asked by At

I'm having a problem with correctly building a container that stores class specimens of different types that are all inheritors of a single abstract class. The register(the container) stores a pointer to the array of these specimens, that has the type of the abstract class. Whenever I try to access data contained in the specimens I only succeed in retrieving parts that can be found in the base class too. For example, an overloaded << used on the register that contains elements of all three inheritors, will only write the abstract class parts on screen, and will neglect anything not present there. Now I don't really know if the problem is with printing out the otherwise correctly stored elements, or the storing is already done in an inappropriate form, so that would be my question: how should this be done properly? Here's the code:

class Register{
private:
int elementNum;
type * pData;
friend std::ostream &operator<<(std::ostream & os,const Register &v);
};
class type{
int a;
int b;
};
class type2: public type{
int c;
int d;
};

The other two inheritors behave the same way as the type2. Here's a part of main:

    int main ()
    {
        type2 A1(1,2,3,4);
        type3 D1(4,5,6,7,8);
        type4 H1(9,10,11,12,13);
        std::cout<<A1<<D1<<H1<<endl;
        Register R1;
        R1.Add(0,A1);
        R1.Add(1,D1);
        R1.Add(2,H1);
        R1.Display();
        R1.MaxLength();
        std::cout<<R1;
        return 0;
    }

Operator << on the register:

std::ostream &operator<<(std::ostream & os,const Register &v){
    for(int i=0;i<v.elementNum;i++)
    {
        os<<v.pData[i]<<endl;
    }
    return os;
}

Only using the << operator or a function from the register ends in this problem. Edit: Implementation of the Add function:

void Register::Add(int position,type& T){
    if(position<0||position>elementNum+1)
        return;
    type *pTemp = new type[elementNum+1];
    if(elementNum==0)
    {
        pTemp[0]=T;
        delete[]pData;
        pData=pTemp;
    }
    else
    {
        for(int i=0,j=0;j<elementNum+1;i++,j++)
        {
            if(position!=j)
                pTemp[j]=pData[i];
            else
            {
                i--;
                pTemp[j]=a;
            }
        }
        delete[]pData;
        pData=pTemp;
    }
    elementNum++;
}
2

There are 2 best solutions below

6
On BEST ANSWER

You can only access public members common to the base class, or virtual method available from the base, polymorphically.

Furthermore, you can only access virtual methods through pointers/references, and you generally can't store different class instances contiguously like you try to do with pData.

If you make a virtual std::ostream &type::dump(std::ostream &os) member method and override is in type2, etc., you can make each overriddinen method show content particular to its sub-type.

struct type {
  virtual ostream &dump(ostream &os) {
    os << a << " " << b << " ";
    return os;
  }
  int a;
  int b;
};

struct type2 : type {
  // Can use parent implementation AND use subtype-specific members:
  ostream &dump(ostream &os) override {
    type::dump(os);
    os << c << " " << d << " ";
    return os;
  }
  int c;
  int d;
};

// This class needs new "void Add(int pos, type &)" logic.
struct Register {
  int   elementNum;
  type *pData; // next hint: this is almost definitely not what you want.
  type **pda;  // probably better (need to use new/delete to make types)
};

ostream &operator<<(ostream &os, Register const &v) {
  for (int i = 0; i < v.elementNum; ++i) {
    // Calls proper virtual method for each instance.
    v.pData[i].dump(os); // XXX probably broken too
    v.pda[i]->dump(os); // should look more like this
    os << endl;
  }
}
2
On
type *pTemp = new type[elementNum+1];

This allocates an array of objects with type type. An object can never change its type, and you cannot replace an element of an array, only modify it. So your Register object never contains objects of any derived classes at all, only those objects with the base class type.

To get an array of heterogeneous objects the hard way, you would need an array of pointers:

type **pTemp = new (type*[elementNum+1]);

To do it the right way, you would shun arrays and raw pointers, and instead use containers and smart pointers:

class Register {
public:
    const type& get(int pos) const;
    type& get(int pos);

    void Add(int pos, const type& obj);
    void Add(int pos, std::unique_ptr<type>&& ptr);

    // ...
private:
    std::vector<std::unique_ptr<type>> m_data;
};

But either way, what pointers do you put in it from your function Add?

void Register::Add(int position,type& T);

Probably not the address &T of the passed reference. Who knows when that object will be destructed. And new type(T) is no good either - it just creates an object of the base type, ignoring the actual type of T. So you'll probably want a clone() method, sometimes called a "virtual copy constructor":

class type {
public:
    using pointer = std::unique_ptr<type>;
    virtual ~type();
    virtual pointer clone() const;
};

type::pointer type::clone() const {
    return pointer(new type(*this));
}

type::pointer type2::clone() const {
    return pointer(new type2(*this));
}

Above I put in two overloads of Add(). The object-passing version goes like:

void Register::Add(int pos, const type& obj) {
    if (pos<0)
        return;
    if (pos >= m_data.size())
        m_data.resize(pos+1);
    m_data[pos] = obj.clone();
}

The other version could be useful if you happen to have a type::pointer already, rather than just an object. With this overload you can just move it into the Register, without needing to clone() anything.

void Register::Add(int pos, type::pointer&& ptr) {
    if (pos<0)
        return;
    if (pos >= m_data.size())
        m_data.resize(pos+1);
    m_data[pos] = std::move(ptr);
}