【问题标题】:I want a vector of derived class pointers as base class pointers我想要一个派生类指针向量作为基类指针
【发布时间】:2013-08-15 21:24:51
【问题描述】:

在 C++ 中,vector 类存储一个对象数组。在这种情况下,我存储指向派生类对象(Dogs)的指针。在某些时候,我想将此向量视为指向基类(动物)对象的指针。这是“正确”/无争议的方式,对吗?为什么我不能这样做?

#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)
  ^

【问题讨论】:

    标签: c++ inheritance


    【解决方案1】:

    公认的解决方案很好,但有一个很大的缺点:它保留了相关向量内容的副本。任何时候更新向量之一,我们都需要更新冗余数据以保持全局状态一致。不是很喜欢,决定尝试解决这个问题(不幸的是发现这需要相当多的工作......):

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

    到目前为止,我只实现了一个前向迭代器,可以提供更多的运算符来进行双向甚至随机访问。此外,我们可能会添加 const 迭代器、(const) 反向迭代器,...

    【讨论】:

      【解决方案2】:

      你可以毫无问题地做你真正想做的事!也就是说,只需这样做:

      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&lt;Dog*&gt;std::vector&lt;Animal*&gt; 完全不同的类

      换句话说,Dog 继承自 Animal,是的,但是 std::vector&lt;X&gt; 不继承自 std::vector&lt;Y&gt;——不管 X 和 Y 是如何相关的!

      模板并没有赋予向量太多智能;他们只是定义了一个新类。你可以这样想:

      class vectorOfDogs {
          Dog* myDogs;
          //...
      }
      
      class vectorOfAnimals {
          Animal* myAnimals;
          //...
      }
      

      vectorOfDogs 是否继承自 vectorOfAnimals?显然不是!但所做的只是将类的名称从 std::vector&lt;Dog*&gt; 更改为 vectorOfDogs

      【讨论】:

        【解决方案3】:

        std::vector 有一个复制构造函数,但它要求您复制完全相同类型的向量。幸运的是,还有另一个构造函数,它接受一对迭代器并添加范围内的所有元素,所以你可以这样做:

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

        这将通过迭代每个 Dog 指针来创建一个新的 Animal 指针向量。每个Dog 指针都会在运行时转换为Animal 指针。

        这是一个更完整的例子(使用 C++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()}
          };
        }
        

        【讨论】:

        • @Mahesh 因为你永远不想能够做到void oops(vector&lt;Animal*&gt;&amp; animals) { animals.push_back(new Cat); } vector&lt;Dog*&gt; dogs; oops(dogs);
        • 这个解决方案有一个缺点:all_animals 维护其他向量内容的 副本,因此如果更新其中一个,all_animals 将不会自动更新...
        • @molbdnilo 这是一个什么样的论点?与void oops(Animal&amp; animal) { animal = *new Cat; } Dog* dog; oops(*dog); 有何不同?
        【解决方案4】:

        您可以将您的 Dog Vector 创建为:

        vector<Animal*> dogs;
        

        并在插入狗之前投射它们

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

        稍后,在访问时回退

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-09-23
          • 2016-02-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-26
          相关资源
          最近更新 更多