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

时间:2015-03-18 20:51:54

标签: c++ pointers polymorphism

我试图设置一个简单的案例来解决课本练习。代码在IDEone上,并在下面重复。

代码是一个简单的例子,试图存储一些动物列表,并能够从我的包装器队列中保存的这些动物特定列表中的一个返回任何通用动物。

我可以添加动物就好了,当我试图找回一只狗时,我似乎得到一个指向狗的指针,因为我可以打印它的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的任何子类添加到我的列表中,并且在那里&#39; sa名为dequeueAny()的特殊函数,它应该能够从任何列表中返回一个随机Animal。代码已经过编辑以包含此内容(以及nullptr时我之前省略的enqueue检查。

处理此问题的最佳方法是什么?它是否传递对现有Animal对象的引用?那会有用吗?即可,我有:

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

不可否认,dequeueDog()之类的内容可能会按值返回Dog

2 个答案:

答案 0 :(得分:3)

您要返回存储的元素的地址(list::front返回引用,并且您正在获取它的地址),然后弹出它(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

答案 1 :(得分:2)

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

您正在指向列表中的前项,删除该项(通过调用pop_front),然后返回悬空指针。