C++多态指针不能调用成员函数

C++ polymorphic pointer cannot invoke member function

我正在尝试设置一个简单案例来解决教科书练习。代码在 IDEone 上,并在下面重复。

该代码是一个简化的案例,它试图存储许多动物列表,并且能够 return 我的包装队列中保存的这些动物特定列表之一中的任何通用动物。

我可以很好地添加动物,并且在尝试找回狗时,我似乎得到了指向狗的指针,因为我可以打印它 name。但是,如果我尝试调用它的成员函数 speak(),代码就会崩溃,我不知道为什么。

class Animal {
public:
    virtual void speak() = 0;
    string name;
};

class Dog: public Animal {
public:
    Dog(string n) { this->name=n; }
    void speak() { cout<<name<<" says WOOF!"<<endl; }
};

class AnimalQueue {
    list<Dog> dogs;
    list<Cat> cats; // etc.
public:
    void enqueue(Animal* a) {
        Dog * d = dynamic_cast<Dog*>(a);
        if (d!=nullptr) dogs.push_back(*d);
        else // check for other animals, etc.
    }
    Dog* dequeueDog() {
        Dog * d = &(dogs.front());
        dogs.pop_front();
        return d;
    }
    Animal dequeueAny() {
        // Should return a random animal from any list
    }
};

int main() {
    // Set up
    AnimalQueue q;
    Dog * d;
    d = new Dog("Rex");
    q.enqueue(d);

    // Retrieve Rex
    d = q.dequeueDog();
    cout<<d->name<<endl;  // Prints "Rex"
    d->speak();           // Crashes?!

    return 0;
}

编辑:抱歉,在减少我的代码时,我消除了问题的本质,即我应该能够将 Animal 的任何子 class 添加到我的列表中,并且有一个名为 dequeueAny() 的特殊函数,它应该能够从任何列表中 return 随机 Animal。代码已被编辑以包含此内容(以及我之前省略的 nullptr 检查 enqueueing.

处理此问题的最佳方法是什么?是传入对现有Animal对象的引用吗?那行得通吗?即可以,我有:

void dequeueAny(Animal * a) {
    // for example, let's return a Dog
    Dog d = dogs.front();
    dogs.pop_front();
    *a = d;
}

诚然,dequeueDog() 之类的值可能 return 一个 Dog

Dog* dequeueDog() {
    Dog * d = &(dogs.front());
    dogs.pop_front();
    return d;
}

你正在获取一个指向列表前面项目的指针,删除那个项目(通过调用pop_front)然后返回一个悬空指针。

您正在返回存储的元素的地址(list::front returns a reference and you're taking the address of it) and then popping it (list::pop_front 销毁 对象):

Dog* dequeueDog() {
    Dog * d = &(dogs.front()); // Take the address of the front object
    dogs.pop_front(); // Destroy the front object
    return d; // Return the address to the deleted object (unsafe state)
}

通过该指针访问内存是undefined behavior

一个可能的解决方案是按值返回您的对象

class Animal {
public:
    virtual void speak() = 0;
    virtual ~Animal() {}; // Always a good thing if the class has virtual members
    string name;
};

class Dog : public Animal {
   ...  // unchanged
};

class AnimalQueue {
    list<Dog> dogs;
public:
    void enqueue(Animal* a) {
        Dog * d = dynamic_cast<Dog*>(a);
        dogs.push_back(*d); // This creates a copy of d and stores it
    }
    Dog dequeueDog() {
        Dog d = dogs.front(); // This creates a copy of the front element
        dogs.pop_front(); // Destroy the front element
        return d;
    }
};

int main() {

    AnimalQueue q;
    Dog * d;
    d = new Dog("Rex");
    q.enqueue(d);

    *d = q.dequeueDog();
    cout << d->name << endl;// Prints "Rex"
    d->speak(); // Prints WOFF

    delete d; // Free your memory

    return 0;
}

Example

请注意,您还忘记释放内存,从而导致内存泄漏。要么使用 smart pointer,要么释放你的记忆,做一个好公民。


编辑:OP 编辑​​了他的问题以指定他还需要一个 dequeueAny 方法。我强烈建议您在回答问题后不要编辑您的问题(要求应尽可能静态)。无论如何,在那种情况下我建议你使用指针(或智能指针)而不是复制对象

class AnimalQueue {
    std::list<Animal*> animals;
public:
    void enqueue(Animal* a) {
        animals.push_back(a); // Copy the pointer
    }
    Animal* dequeue() {
        Animal *d = animals.front();
        animals.pop_front(); // Destroy the pointer
        return d;
    }
};

int main() {
    AnimalQueue q;
    std::unique_ptr<Dog> d = std::make_unique<Dog>("Rex");
    q.enqueue(d.get()); // This will now store the pointer to the object

    // Try with dog-specific command
    Animal *sameDog = q.dequeue(); // d.get() and sameDog are now pointing at the same object
    if (d.get() == sameDog)
        std::cout << "d.get() == sameDog" << std::endl;
    std::cout << sameDog->name << std::endl;// Prints "Rex"
    sameDog->speak();

    return 0;
}

Example