【问题标题】:How to declare a vector list of abstract class in C++? [duplicate]如何在 C++ 中声明抽象类的向量列表? [复制]
【发布时间】:2020-03-19 18:58:55
【问题描述】:

我有几个类继承自一个主类。为了简单起见,我过度简化了类定义以使其简短明了。

动物.h

所有其他类继承自的主类:

class Animal {
protected:
    string name;
public:
    Animal(string name);
    virtual string toString() { return "I am an animal"; }
};

鸟.h

class Bird: public Animal {
private:
    bool canFly;
public:
    Bird(string name, bool canFly = true) 
     : Animal(name)   // call the super class constructor with its parameter
    {
        this->canFly = canFly;
    }
    string toString() { return "I am a bird"; }
};

indect.h

class Insect: public Animal {
private:
    int numberOfLegs;
public:
    Insect(string name, int numberOfLegs) : Animal(name) {
        this->numberOfLegs = numberOfLegs;
    }
    string toString() { return "I am an insect."; }
};

现在,我需要声明一个vector<Animal>,它将包含每个继承类的多个实例。

main.cpp

#include <iostream>
#include "animal.h"
#include "bird.h"
#include "insect.h"

// assume that I handled the issue of preventing including a file more than once
// using #ifndef #define and #endif in each header file.

int main() {

    vector<Animal> creatures;

    creatures.push_back(Bird("duck", true));
    creatures.push_back(Bird("penguin", false));
    creatures.push_back(Insect("spider", 8));
    creatures.push_back(Insect("centipede",44));

    // now iterate through the creatures and call their toString()

    for(int i=0; i<creatures.size(); i++) {
        cout << creatures[i].toString() << endl;
    }
}

我希望得到以下输出:

我是一只鸟

我是一只鸟

我是昆虫

我是昆虫

但我得到了:

我是动物

我是动物

我是动物

我是动物

我知道这与“矢量生物;. It is calling the constructor for Animal. But my intention is to tell the compiler, that this creaturespoints to an array ofAnimalinherited classes, might beBirdmight beinsect, the point is: they all implement their own unique respective version of toString()` 行有关。

如何声明从同一祖先继承的对象的多态数组?

【问题讨论】:

  • 你面临的是对象切片问题
  • @ThePhilomath 你愿意详细说明一下,还是指出我可以在哪里解决这个问题?
  • 问题在于creatures.push_back(Bird("duck", true)); 您正在创建一个 Bird 对象并将其复制到 Animal 对象中。一种方法是动态创建对象,以便可以使用 vtable 解析正确的函数调用
  • 创建一个指向 Animal 的指针向量:vector&lt;Animal*&gt;

标签: c++ inheritance vector polymorphism


【解决方案1】:

你不能使用 value 语义(阅读object slicing)。您必须使用指针。

例子:

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Animal
{
 protected:
  std::string name;

 public:
  Animal(std::string name) : name(name)
  {
  }
  virtual std::string toString()
  {
    return "I am an animal";
  }
  virtual ~Animal()
  {
  }
};

class Bird : public Animal
{
 private:
  bool canFly;

 public:
  Bird(std::string name, bool canFly = true) : Animal(name)  // call the super class constructor with its parameter
  {
    this->canFly = canFly;
  }
  std::string toString()
  {
    return "I am a bird";
  }
};

class Insect : public Animal
{
 private:
  int numberOfLegs;

 public:
  Insect(std::string name, int numberOfLegs) : Animal(name)
  {
    this->numberOfLegs = numberOfLegs;
  }
  std::string toString()
  {
    return "I am an insect.";
  }
};

int main()
{
  std::vector<std::unique_ptr<Animal>> creatures;

  creatures.emplace_back(new Bird("duck", true));
  creatures.emplace_back(new Bird("penguin", false));
  creatures.emplace_back(new Insect("spider", 8));
  creatures.emplace_back(new Insect("centipede", 44));

  // now iterate through the creatures and call their toString()

  for (int i = 0; i < creatures.size(); i++)
  {
    std::cout << creatures[i]->toString() << std::endl;
  }
}

打印:

I am a bird
I am a bird
I am an insect.
I am an insect.

我也推荐阅读Sean parent Run Time Polymorphism。思路如下:

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Animal
{
 public:
  struct Interface
  {
    virtual std::string toString() const = 0;
    virtual ~Interface()                 = default;
  };
  std::shared_ptr<const Interface> _p;

 public:
  Animal(Interface* p) : _p(p)
  {
  }
  std::string toString() const
  {
    return _p->toString();
  }
};

class Bird : public Animal::Interface
{
 private:
  std::string _name;
  bool        _canFly;

 public:
  Bird(std::string name, bool canFly = true) : _name(name), _canFly(canFly)
  {
  }
  std::string toString() const override
  {
    return "I am a bird";
  }
};

class Insect : public Animal::Interface
{
 private:
  std::string _name;
  int         _numberOfLegs;

 public:
  Insect(std::string name, int numberOfLegs)
      : _name(name), _numberOfLegs(numberOfLegs)
  {
  }
  std::string toString() const override
  {
    return "I am an insect.";
  }
};

int main()
{
  std::vector<Animal> creatures;

  creatures.emplace_back(new Bird("duck", true));
  creatures.emplace_back(new Bird("penguin", false));
  creatures.emplace_back(new Insect("spider", 8));
  creatures.emplace_back(new Insect("centipede", 44));

  // now iterate through the creatures and call their toString()

  for (int i = 0; i < creatures.size(); i++)
  {
    std::cout << creatures[i].toString() << std::endl;
  }
}

【讨论】:

    【解决方案2】:

    问题在于creatures.push_back(Bird("duck", true)); 您正在创建一个 Bird 对象并将其复制到 Animal 对象中。 一种方法是动态创建对象,以便可以使用 vtable 解析正确的函数调用。 修改这部分代码,就可以正常工作了。

    vector<Animal *> creatures;
    
        creatures.push_back(new Bird("duck", true));
        creatures.push_back(new Bird("penguin", false));
        creatures.push_back(new Insect("spider", 8));
        creatures.push_back(new Insect("centipede",44));
    

    编辑:确保在creatures 超出范围之前释放内存。

    【讨论】:

    • 由于指针是使用new 创建的,一旦你完成了向量,就必须使用delete 释放它们。但是,使用像 unique_ptrshared_ptr 这样的智能指针可以避免手动内存管理。
    • @jignatius 是的,你必须这样做。由于该问题未使用 C++11 或更高版本标记,因此我无法建议使用智能指针的解决方案。
    • 好的。我只是想我会把它指向 OP。
    【解决方案3】:

    C++ 对象是具有特定类型的。这与许多语言不同,其中变量总是保存对对象的引用,因此它们可以很容易地保存对派生对象的引用。

    如果将派生类的实例复制到基类的对象上,则会得到切片:仅复制基类数据,受让人的类型仍然是受让人的类型基类。

    要在 C++ 中实现多态行为,您需要使用 std::variant 指定允许的可能性,在这种情况下,对象将持有其中一个选项,并在分配时在它们之间切换类型,或者您需要使用一个指向基类的指针,它可以保存一个指向任何派生类型的指针,但是你必须警惕内存泄漏。不过,您确实需要使用 std::visitstd::get 来检索这些值。

    如果您要使用指针,您应该始终使用std::shared_ptrstd::unique_ptr 来管理对象以避免内存泄漏。

    带有变体的代码:

    int main() {
    
        vector<std::variant<Bird,Insect>> creatures;
    
        creatures.push_back(Bird("duck", true));
        creatures.push_back(Bird("penguin", false));
        creatures.push_back(Insect("spider", 8));
        creatures.push_back(Insect("centipede",44));
    
        // now iterate through the creatures and call their toString()
    
        for(int i=0; i<creatures.size(); i++) {
            cout << std::visit([](auto const& creature){
                return creature.toString();
            },creatures[i]) << endl;
        }
    }
    

    带指针的代码:

    int main()
    {
      std::vector<std::unique_ptr<Animal>> creatures;
    
      creatures.emplace_back(std::make_unique<Bird>("duck", true));
      creatures.emplace_back(std::make_unique<Bird>("penguin", false));
      creatures.emplace_back(std::make_unique<Insect>("spider", 8));
      creatures.emplace_back(std::make_unique<Insect>("centipede", 44));
    
      // now iterate through the creatures and call their toString()
    
      for (int i = 0; i < creatures.size(); i++)
      {
        std::cout << creatures[i]->toString() << std::endl;
      }
    }
    

    std::shared_ptr 的代码是等效的:只需将 unique 替换为 shared

    【讨论】:

      猜你喜欢
      • 2016-05-10
      • 2013-03-04
      • 2014-02-26
      • 2016-05-10
      • 2011-01-23
      • 1970-01-01
      • 2019-12-27
      • 1970-01-01
      • 2012-10-31
      相关资源
      最近更新 更多