【问题标题】:C++ polymorphic pointer cannot invoke member functionC++多态指针不能调用成员函数
【发布时间】:2015-03-19 16:53:23
【问题描述】:

我正在尝试设置一个简单的案例来解决教科书练习。代码在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 的任何子类添加到我的列表中,并且有一个名为@987654326 的特殊函数@ 应该能够从任何列表中返回一个随机的 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() 之类的东西可能应该按值返回 Dog

【问题讨论】:

  • 为什么 Animal 没有一个构造函数,它的名字是 dog 调用的?如果enqueue只能合理处理狗,那你为什么不把它的参数设为Dog *呢?您的代码也会泄漏内存。
  • @NeilKirk:在这种情况下,应该将其重命名为enqueueDog(),以匹配dequeueDog()
  • 如果将不是DogAnimal 传递给enqueue()dynamic_cast 将返回一个空指针,您将其存储在列表中,但您不检查任何地方。并且dequeueDog() 也不会在弹出之前检查列表是否为空。
  • 考虑std::list&lt;std::shared_ptr&lt;Dog/Animal&gt;&gt;
  • 尼尔是对的。 main() 正在泄漏 d,因为 enqueue() 复制 动物不理会原件,而 main() 没有调用 delete d。这段代码绝对应该使用std::list&lt;std::shared_ptr&lt;Animal&gt;&gt;(或std::list&lt;std::unique_ptr&lt;Animal&gt;&gt;),并停止复制所有内容。

标签: c++ pointers polymorphism


【解决方案1】:

您正在返回存储的元素的地址(list::front 返回一个引用并且您正在获取它的地址)然后弹出它(list::pop_front destroys 对象):

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

【讨论】:

  • 抱歉,我最初的问题不清楚。所以Dog dequeueDog() 会起作用,这很好,但我也希望能够dequeueAny(),返回任何随机的Animal(见编辑问题)。注意到内存泄漏。
  • @Alec 编辑了问题。无论如何,我建议您不要再次编辑要求(这会使此处给出的答案无效),而是提出另一个不同的问题
【解决方案2】:
Dog* dequeueDog() {
    Dog * d = &(dogs.front());
    dogs.pop_front();
    return d;
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-26
    • 1970-01-01
    • 2010-12-01
    相关资源
    最近更新 更多