Я хочу, чтобы вектор указателей производного класса назывался указателями базового класса

В С++ класс vector хранит массив объектов. В этом случае я сохраняю указатели на объекты класса класса (Собаки). В какой-то момент я хочу рассматривать этот вектор как указатели на объекты базового класса (Животные). Это "правильный" /не противоречивый способ? Почему я не могу это сделать?

#include <vector>
using namespace std;

class Animal { }; 
class Dog : public Animal { };

int main(int argc, char *argv[]) {
    vector<Dog*> dogs;
    dogs.push_back(new Dog());
    dogs.push_back(new Dog());
    vector<Animal*> animals = dogs; // This doesn't seem to work.

    // This is really what I want to do...
    vector<Animal*> all_animals[] = {dogs, cats, birds};
}

Ошибка:

Untitled.cpp:11:18: error: no viable conversion from 'vector<class Dog *>' to 'vector<class Animal *>'
    vector<Animal*> animals = dogs;
                    ^         ~~~~
/usr/include/c++/4.2.1/bits/stl_vector.h:231:7: note: candidate constructor not viable: no known conversion from 'vector<Dog *>' to 'const std::vector<Animal *, std::allocator<Animal *> > &' for 1st argument
  vector(const vector& __x)
  ^

Ответ 1

Существует конструктор копирования для std::vector, но для этого требуется скопировать тот же самый тип вектора. К счастью, есть еще один конструктор, который берет пару итераторов и добавляет все элементы в диапазоне, поэтому вы можете сделать это:

vector<Animal*> animals(dogs.begin(),dogs.end());

Это создает новый вектор указателей Animal путем итерации через каждый указатель Dog. Каждый указатель Dog преобразуется в указатель Animal, когда он идет.

Вот более полный пример (с использованием С++ 11):

#include <vector>

struct Animal { };

struct Dog : Animal { };

struct Cat : Animal { };

struct Bird : Animal { };

int main(int,char**)
{
  Dog dog1, dog2;
  Cat cat1, cat2;
  Bird bird1, bird2;
  std::vector<Dog *> dogs = {&dog1,&dog2};
  std::vector<Cat *> cats = {&cat1,&cat2};
  std::vector<Bird *> birds = {&bird1,&bird2};
  std::vector<std::vector<Animal *>> all_animals = {
    {dogs.begin(),dogs.end()},
    {cats.begin(),cats.end()},
    {birds.begin(),birds.end()}
  };
}

Ответ 2

Вы можете создать свой вектор для собак как:

vector<Animal*> dogs;

И бросьте свои объекты для собак, прежде чем вставлять их

dogs.push_back((Animal*)new Dog());

Позже, отбрасывайте при доступе к

Ответ 3

Вы можете делать то, что действительно хотите, без проблем! То есть просто:

class Animal {
   public:
      std::string GetNoise() const = 0;
};
class Dog : public Animal {
   public:
      std::string GetNoise() const { return "Bark!"; }
};
class Cat : public Animal {
   public:
      std::string GetNoise() const { return "Meow"; }
      bool        LikesSleeping() const { return true; }
};

Dog* d = new Dog;
Cat* c = new Cat;
vector<Animal*> all_animals;
all_animals.push_back(d, c);

// then, later...

// this will print "Bark!"
std::cout << all_animals[0]->GetNoise() std::endl;

// if you know the type somehow
Cat* x = dynamic_cast<Cat*>(all_animals[1]);
const bool y = x->LikesSleeping();

Причина, по которой ваш код не работает так, как вы ожидаете, заключается в следующем: std::vector<Dog*> является полностью другим классом из std::vector<Animal*>.

Другими словами, Dog наследует от Animal, да, но a std::vector<X> не наследует от std::vector<Y> - независимо от того, как связаны X и Y!

Шаблоны не дают вектору большой интеллект; они просто определяют новый класс. Вы можете думать об этом так:

class vectorOfDogs {
    Dog* myDogs;
    //...
}

class vectorOfAnimals {
    Animal* myAnimals;
    //...
}

Использует ли vectorOfDogs inhert из vectorOfAnimals? Ясно, нет! Но все, что было сделано, - это изменение имени класса от std::vector<Dog*> до vectorOfDogs.

Ответ 4

Принятое решение хорошо, но имеет один большой недостаток: оно поддерживает копии содержимого векторов, о которых идет речь. Каждый раз, когда обновляется один из векторов, нам также необходимо обновлять избыточные данные, чтобы поддерживать согласованность глобального состояния. Не очень любя, решил попытаться обойти эту проблему (к сожалению, нужно было обнаружить, что это требует довольно большой работы...):

class AllAnimals
{
    struct Wrapper
    {
        virtual ~Wrapper() { }
        virtual Animal* begin() = 0;
        virtual Animal* end() = 0;
    };

    template <typename T>
    struct SpecificWrapper : Wrapper
    {
        T& animals;
        SpecificWrapper(T& animals)
                : animals(animals)
        { }
        Animal* begin() override
        {
            return *animals.begin();
        }
        Animal* end() override
        {
            return *animals.end();
        }
    };

    std::vector<std::unique_ptr<Wrapper>> wrappers;

public:
    class iterator : public std::iterator<std::forward_iterator_tag, Animal*>
    {
        friend class AllAnimals;
        decltype(wrappers)::iterator current, end;
        Animal* animal;
        iterator(decltype(current) begin, decltype(end) end)
                : current(begin), end(end)//, animal(nullptr)
        {
            while(current != end && (*current)->begin() == (*current)->end())
            {
                ++current;
            }
            animal = current == end ? nullptr : (*current)->begin();
        }
    public:
        bool operator==(iterator const& other)
        {
            return current == other.current && animal == other.animal;
        }
        bool operator!=(iterator const& other)
        {
            return !(*this == other);
        }
        iterator& operator++()
        {
            if(++animal == (*current)->end())
            {
                ++current;
                animal = current == end ? nullptr : (*current)->begin();
            }
            return *this;
        }
        iterator operator++(int)
        {
            iterator i(*this);
            ++*this;
            return i;
        }
        Animal* operator*()
        {
            return animal;
        }
        Animal* operator->()
        {
            return animal;
        }
    };

    iterator begin()
    {
        return iterator(wrappers.begin(), wrappers.end());
    }
    iterator end()
    {
        return iterator(wrappers.end(), wrappers.end());
    }

    template <typename T>
    void push_back(std::vector<T*>& v)
    {
        wrappers.emplace_back(new SpecificWrapper<decltype(v)>(v));
    }
};

До сих пор я реализовал только прямой итератор, можно было бы предоставить другим операторам возможность сделать двунаправленный или даже произвольный доступ. Кроме того, мы могли бы добавить константные итераторы, (константные) обратные итераторы,...