2015-03-18 29 views
1

我正在嘗試設置一個簡單的例子來解決教科書練習。代碼在IDEone,並在下面重複。C++多態指針不能調用成員函數

該代碼是試圖存儲一系列動物列表的簡化案例,並且能夠從我的封裝隊列中保存的這些動物特定列表之一返回任何通用動物。

我可以添加動物很好,當試圖檢索一隻狗時,我似乎得到一個指向狗的指針,因爲我可以打印它的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添加到我的列表的精髓,並有一個名爲特殊功能dequeueAny()應該能夠從任何列表中隨機返回Animal。該代碼已經被編輯成包括這個(以及我之前省略的nullptr檢查時enqueue ING。

什麼是處理這個最好的方法是什麼?難道是在參考傳遞給現有的Animal對象?將這項工作?即可以,我有:

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

誠然,像dequeueDog()也許應該返回Dog按值

+1

爲什麼沒有動物有一個構造函數的名字這狗電話?如果'enqueue'只能合理地處理狗,爲什麼不用參數'Dog *'?你的代碼也會泄漏內存。 – 2015-03-18 20:53:46

+0

@NeilKirk:在這種情況下,應該將它重命名爲enqueueDog(),以匹配'dequeueDog()'。 – 2015-03-18 20:59:51

+0

如果不是'Dog'的'Animal'傳遞給'enqueue()','dynamic_cast'將返回一個NULL指針,這個指針將被存儲在列表中,但是你不會檢查那個地方。 'dequeueDog()'在彈出之前也不檢查列表是否爲空。 – 2015-03-18 21:00:23

回答

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

+0

對不起,我不清楚我的原始問題。所以'Dog dequeueDog()'會起作用,這很好,但我也想'dequeueAny()',返回任何隨機的'Animal'(見編輯問題)。內存泄漏記錄。 – Alec 2015-03-19 07:20:21

+0

@Alec編輯了這個問題。無論如何,我建議你不要再次編輯要求(它使給出的答案無效),而是要求另一個不同的問題 – 2015-03-19 08:54:05

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

有你正在服用的興趣點。移到列表中的前端項目,刪除該項目(通過調用pop_front),然後返回一個懸掛指針。