Создание / управление гетерогенным контейнером (C++)

У меня проблема с правильным построением контейнера, в котором хранятся образцы классов разных типов, которые являются наследниками одного абстрактного класса. Регистр (контейнер) хранит указатель на массив этих образцов, который имеет тип абстрактного класса. Всякий раз, когда я пытаюсь получить доступ к данным, содержащимся в образцах, я получаю только те части, которые можно найти в базовом классе. Например, перегруженная <<, используемая в регистре, который содержит элементы всех трех наследников, будет записывать только абстрактные части класса на экране и пренебрегать ничем, чего там нет. Теперь я действительно не знаю, связана ли проблема с распечаткой правильно сохраненных элементов, или сохранение уже выполнено в неподходящей форме, так что это был бы мой вопрос: как это должно быть сделано правильно? Вот код:

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;
};

Два других наследника ведут себя так же, как и type2. Вот часть основного:

    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;
    }

Оператор << в реестре:

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

Только использование оператора<< или функции из регистра заканчивается в этой проблеме. Редактировать: Реализация функции Добавить:

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 ответа

Решение

Вы можете получить доступ только к открытым элементам, общим для базового класса, или к виртуальному методу, доступному из базы, полиморфно.

Кроме того, вы можете получить доступ к виртуальным методам только через указатели / ссылки, и вы, как правило, не можете хранить различные экземпляры классов непрерывно, как вы пытаетесь делать с pData ,

Если вы сделаете virtual std::ostream &type::dump(std::ostream &os) метод-член и переопределение в type2 и т. д., вы можете сделать так, чтобы каждый переопределенный метод показывал содержимое, относящееся к его подтипу.

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;
  }
}
type *pTemp = new type[elementNum+1];

Это выделяет массив объектов с типом type, Объект никогда не может изменить свой тип, и вы не можете заменить элемент массива, только измените его. Так что ваши Register объект вообще никогда не содержит объектов каких-либо производных классов, только те объекты, которые имеют тип базового класса.

Чтобы получить массив разнородных объектов сложным способом, вам понадобится массив указателей:

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

Чтобы сделать это правильно, вы должны избегать массивов и необработанных указателей, а вместо этого использовать контейнеры и умные указатели:

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;
};

Но в любом случае, какие указатели вы положили в него из своей функции Add?

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

Вероятно, не адрес &T прошедшей ссылки. Кто знает, когда этот объект будет разрушен. А также new type(T) тоже не годится - он просто создает объект базового типа, игнорируя фактический тип T, Так что вы, вероятно, захотите clone() метод, иногда называемый "конструктор виртуальной копии":

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));
}

Выше я положил в двух перегрузках Add(), Версия для передачи объекта выглядит так:

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();
}

Другая версия может быть полезна, если у вас есть type::pointer уже, а не просто объект. С этой перегрузкой вы можете просто переместить ее в Registerбез необходимости clone() что-нибудь.

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);
}
Другие вопросы по тегам