Получить данные из разнородного std::list

В приведенном ниже коде я просто пытаюсь поэкспериментировать с гетерогенным списком std::, где я сохранил три объекта класса Derived в списке типа Base*. При получении данных из списка у меня возникли некоторые проблемы. Как мне это сделать? Приведенный ниже код работает, потому что все три класса идентичны по определению. Таким образом, компилятор каким-то образом сумел дать мне ожидаемый результат.

#include <list>
#include <iostream>
#include <string.h>

class Base;
typedef std::list<Base*> any_list;

class Base{};

class Derived1 : public Base
{
public:
    std::string s;
    Derived1():s("D1"){}
};
class Derived2 : public Base
{
public:
    std::string s;
    Derived2():s("D2"){}
};
class Derived3 : public Base
{
public:
    std::string s;
    Derived3():s("D3"){}
};

int main(int argc, char **argv) {
    any_list l;
    l.push_back(new Derived1);
    l.push_back(new Derived2);
    l.push_back(new Derived3);

    for(any_list::iterator itr=l.begin();itr!=l.end();++itr)
        std::cout<<((Derived1*)*itr)->s;
}

Обратите внимание, что o/p -

D1 D2 D3

Теперь это не работает, если я добавлю одного дополнительного члена в любой из классов (это ожидается и правильно). Итак, как я должен типизировать данные и извлечь их из разнородного списка? Я что-то здесь упускаю?

4 ответа

Проще всего определить Base следующим образом:

class Base {
public:
    virtual const std::string& getData() = 0;
}

И затем ваши различные производные классы реализуют getData() по мере необходимости. Таким образом, ваш цикл вывода может быть просто:

for (Base* b : l) {
    cout << b->getData();
}

Я бы переопределил Base иметь виртуальный способ вывода информации:

class Base {
    friend std::ostream & operator << (std::ostream &os, const Base &b) {
        b.print(os);
        return os;
    }
    virtual void print (std::ostream &os) const = 0;
public:
    virtual ~Base () {}
};

Затем вы вынуждены реализовать print метод в каждом производном классе. Ваш цикл печати будет выглядеть так:

    for (any_list::iterator itr=l.begin();itr!= l.end();++itr)
        std::cout << **itr;

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

typedef std::list< std::unique_ptr<Base> > any_list;

поскольку unique_ptr требует явной конструкции из указателя, ваш код, который заполняет список, должен быть обновлен.

    l.push_back(std::unique_ptr<Base>(new Derived1));
    l.push_back(std::unique_ptr<Base>(new Derived2));
    l.push_back(std::unique_ptr<Base>(new Derived3));

Ваш актерский состав (Derived1*)*itr эквивалентно static_cast в котором говорится:

Если значение типа "указатель на cv1" BУказывает на B это на самом деле подобъект объекта типа Dрезультирующий указатель указывает на включающий объект типа D, В противном случае результат приведения не определен.

Таким образом, у вас неопределенное поведение, потому что вы используете указатели, которые указывают на Derived2 а также Derived3 возражает против Derived1*,

Правильный способ достичь этого - это иметь std::string s; определяется в Base и дать ему конструктор, который принимает std::string аргумент. Тогда вы можете сделать конструкторы Derived1 в Derived3 вызовите этот конструктор с соответствующим строковым аргументом.

Если вы добавите каких-либо дополнительных членов к одному из Derived классы, вы не сможете получить к ним доступ через полиморфный Base*, Однако попытка сделать это является признаком плохого дизайна. Вы должны только обрабатывать объекты, которые Base* указывает на то, как будто они были Base объекты. То есть, Base описывает интерфейс для этих объектов.

Если вам действительно нужно сделать что-то, что является производным классом, специфичным для объекта, указанного Base*Вы можете использовать dynamic_cast:

if (Derived1* p = dynamic_cast<Derived1*>(*itr)) {
  // This condition will only be true if the object pointed to by `*itr` really is a Derived1
}
#include <list>
#include <vector>
#include <iostream>
#include <string.h>

class Base;
typedef std::list<Base*> any_list;

class Base{
public:
    virtual ~Base(){}
    virtual std::string getData()=0;
};

class Derived1 : public Base
{
public:
    std::string s;
    Derived1():s("D1"){}
    std::string getData()
    {
        return s;
    }
};
class Derived2 : public Base
{
public:
    std::string s;
    Derived2():s("D2"){}
    std::string getData()
    {
        return s;
    }
};
class Derived3 : public Base
{
public:
    std::string s;
    Derived3():s("D3"){}
    std::string getData()
    {
        return s;
    }
};

int main(int argc, char **argv) {
    any_list l;
    l.push_back(new Derived1);
    l.push_back(new Derived2);
    l.push_back(new Derived3);

    for(any_list::iterator itr=l.begin();itr!=l.end();++itr)
        std::cout<<((Base*)*itr)->getData()<<std::endl;
}

Спасибо за помощь. Путаница понятна и проблема решена. Кстати, что это -

typedef std:: list any_list;

Я не нашел ничего под названием unique_ptr в std:: Пространство имен.:(

Другие вопросы по тегам