【问题标题】:Inheritance issue with pointer to objects [closed]指向对象的指针的继承问题[关闭]
【发布时间】:2021-05-24 21:01:09
【问题描述】:

我正在设计一个简单的跟踪程序,它构建了一个指向代表动物园中动物的对象的指针向量。

我将我的向量声明为:vector<Animal*> zooAnimals,Animal* 是指向 Animal 类的指针。

问题在于从Animal 继承子类。例如,Animal 是 Mammal 的父级,而后者又是 Bat 的父级。但是,当我创建 Bat* 并将其添加到向量时,它并没有拾取它应该继承的所有其他字段。它只接收来自Animal(跟踪号和动物名称)的字段,但没有来自Mammal(type、subType 和nursingStatus)的字段。

我设置它的方式是我的程序调用一个函数createNewZooAnimal(),它创建并返回一个Animal*(指向动物类型对象的指针),通过(理论上)调用动物子的特定构造函数键入并返回指向新创建对象的指针(即Crocodile* 或Bat* 等)。但是,这些新创建的指针不是从Mammal/Oviparous 或继承自它们自己的类的特定构造函数。我的头文件似乎没有任何错误,实现它们的 cpp 文件也没有。系统会愉快地创建我需要的特定指针,但不会继承最基本的类Animal之外的任何东西。

Animal* createNewZooAnimal(int trackingNum, string name, string type, string subType, int numEggs, int nursing) {
    
//using a simple if-else if structure here, we cover each sub-type, calling the constructor for each to create the pointer for each object

    if (subType == "Crocodile") {
        Crocodile* croc1 = new Crocodile(numEggs); //calls constructor and creates a pointer to the new object of class Crocodile
        croc1->trackingNum = trackingNum;
        croc1->name = name;
        return croc1; // returns pointer to get included in the vector
    }

    else if (subType == "Goose") {
        Goose* goose1 = new Goose(numEggs); //calls the constructor for Goose, passing in numEggs, and assigns a pointer to the location of the new object
        goose1->trackingNum = trackingNum;
        goose1->name = name;
        return goose1; //returns pointer to get included in the vector
    }

    else if (subType == "Pelican") { //constructs new pelican object and returns its pointer
        Pelican* pelican1 = new Pelican(numEggs);
        pelican1->trackingNum = trackingNum;
        pelican1->name = name;
        return pelican1;
    }


    else if (subType == "Bat") { //constructs new bat object and returns its pointer
        Bat* bat1 = new Bat(nursing);
        bat1->trackingNum = trackingNum;
        bat1->name = name;
        return bat1;
    }

    else if (subType == "Whale") { //constructs a new whale object and returns its pointer
        Whale* whale1 = new Whale(nursing);
        whale1->trackingNum = trackingNum;
        whale1->name = name;
        return whale1;
    }

    else if (subType == "SeaLion") {  //constructs a new sea lion object and returns it pointer
        SeaLion* seaLion1 = new SeaLion(nursing);
        seaLion1->trackingNum = trackingNum;
        seaLion1->name = name;
        return seaLion1;
    }

我设置的方式是用户将输入有关动物的信息(名称、跟踪号、类型(哺乳动物/卵生)、子类型(蝙蝠、鲸鱼等)...)然后通过指向指针创建者createNewZooAnimal 来调用动物子类型的特定构造函数。然后该函数返回一个指向该对象的指针,然后将其推送到向量上:

cout << "Enter 'Y' or 'y' to save this animal to the system, or 'N' or 'n' to cancel: " << endl;
cin >> addAnimalConfirm;
  if (addAnimalConfirm == 'Y' || addAnimalConfirm == 'y') { // if user confirms addition of animal, parameters are passed
                                                            // to pointer return function, where a new animal object is created
                                                            // and we receive the pointer to it.  This is then pushed onto our vector
                                                            // of pointers
      Animal* temp = createNewZooAnimal(trackingNum, name, type, subType, numEggs, nursing);
      zooAnimals.push_back(temp);
      cout << "Animal added to Wildlife Zoo's system successfully." << endl;
  }

这是 Animal.h 代码:

class Animal{
public:
    std::string name;
    int trackingNum;

    Animal() {
        trackingNum = 0;
        name = " ";

    }
};

还有 Mammal.h,例如:

class Mammal : public Animal {
public:
    Mammal();
    Mammal(int nursingIndicator);
    void displayAnimalData();
    std::string type;
    std::string subType;
    int getNursing();
    void setNursing(int numberEggs);

private:
    int nursing;
};

和 Bat.h:

class Bat : public Mammal {
public:
    Bat();
    Bat(int nursingIndicator);
};

【问题讨论】:

  • 请发帖minimal reproducible example。有问题的部分似乎是“但是当我创建 Bat* 并将其添加到向量时,它不会在它应该继承的所有其他字段上拾取”,但该部分不在您发布的代码中。您发布的内容看起来像工厂功能,看起来还不错
  • 如果您有一个指向基类型的指针,该指针指向一个更派生的对象,则不能使用该指针访问该对象的更多派生部分。如果你有一个Animal*,那么你所知道的就是指向的对象是Animal 的某种形式,所以这就是你可以访问的所有对象的Animal 部分。目前尚不清楚你想用你的向量实现什么。此问题的正确解决方案取决于您要执行的操作。
  • 更严重的是,存储原始指针以后可能会导致所有权问题,您应该考虑使用 smart 指针(unique_ptr 或 shared_ptr)。但它们不应导致对象切片。我认为切片的出现是因为或您使用这些指针的方式。
  • 为什么你认为有问题?你没有在问题中显示它。是不是你观察到内存泄漏?我认为您所展示的构建过程无法正确删除。我看到的唯一方法是通过存储在zooAnimals 中的指针,这实际上会做错事。
  • 忘记向量。制作一个指向蝙蝠的动物*。现在,使用 Animal* 您可以访问 Animal 拥有的任何成员函数或数据,如果它是虚拟的,它将使用派生类代替。如果您需要特定的 Bat 东西(例如 EchoLocate() 或其他东西),您需要先知道该对象是 Bat,然后才能执行此操作 - 因此其中一个虚拟功能应该是“类型”,以便您知道自己是哪种动物正在指向。然后,您可以创建一个 Bat* 指向它并访问您想要的任何内容。

标签: c++ pointers inheritance


【解决方案1】:

您构建的通用接口(基类)可以命名为“直接成员访问接口”。

这是一种可能但非常有限的方法,因为编译器静态实现成员访问,因为它使用添加到每个对象的基地址的成员偏移量。动态访问是通过虚拟方法表 (VMT) 完成的,这些表包含virtual 函数的地址,每个对象存储一个指向其实际类表的指针作为隐藏成员变量。对于每个成员(无论是否隐藏)都是正常的,派生类将此成员放置在完全相同的偏移量上,因此派生类的处理方式相同。换句话说,完全相同的类的实例共享相同的 VMT 地址。

good answer by largest_prime_is_463035818 向您展示了一个由三个纯虚函数组成的动态 通用接口的有用示例。为了实现实际动物类型的行为,相应的类必须为eat 或sleep 提供如何 的实现。将getName() 功能也设为纯虚拟的决定更符合审美。

查看您的代码,我会说void displayAnimalData(); 函数是从Mammal 移动到Animal 并转换为(纯)虚函数的理想选择。这样您就可以开始探索polymorphy,而无需过多更改您目前拥有的内容:

class Animal {

public:
    std::string name;
    int trackingNum;

    // a placeholder to be implemented by specific animal classes
    virtual void displayAnimalData()=0;    

    // this way we clean up the actual class through the common interface
    virtual ~Animal() {}

};

通过提供一个虚拟析构函数,我们可以通过基类接口调用析构函数来处理实际类的实例(并进行特定的清理),即如果我们有一些类型(Animal* animal), delete animal 会做正确的事,否则,至少会出现内存泄漏。当然,在这里公开公共成员变量仍然是一个问题。

[编辑]重做:基类中的虚拟析构函数比受保护的构造函数更重要。

【讨论】:

    【解决方案2】:

    正确的方法是设计基类,使其覆盖所有Animals 的公共接口。例如,您可以:

    struct Animal {
        virtual void eat() = 0;
        virtual void sleep() = 0;
        virtual std::string getName() = 0;
        // ... more ...
    };
    

    这就是Animals 可以做什么的定义。然后,不同的子类可以提供此接口的不同实现。您可以创建指向子类实例的(智能)指针向量。他们都可以有不同的方式来sleep 或eat,但Animal 只能做Animal 可以做的事情。

    【讨论】:

    • 谢谢,项目指南指出 Animal 类应该是抽象的,但我不知道如何做到这一点,因为没有函数,只有变量(名称和 trackingNumber)
    • @faangorn 当一个类至少有一个纯虚方法时,它被称为抽象类,在我的示例中,所有方法都是纯虚方法。
    • 既然 name 和 trackingNum 是私有的,我应该能够将访问器设置为虚拟,程序将在运行时实现所需的特定构造函数?
    • @faangorn 我的印象是你目前正试图理解并一次做太多事情。构造函数不是“在运行时实现的”。实际上,在你的问题得到澄清之前,我没有给你一个很大的帮助。我建议你回到一个更简单的例子,也许是一个子类。然后你可以准备一个minimal reproducible example 并在这里发布。您发布的代码中缺少太多内容,无法为您提供完整的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-30
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-28
    • 1970-01-01
    相关资源
    最近更新 更多