2013-10-05 1 views
2

Je suis relativement nouveau à C++, et provenant d'un arrière-plan C# Je vais avoir du mal avec cette liste itération:Itère une liste des sous-classes erreur

J'ai une méthode qui fait une boucle à travers une liste d'objets et d'appels une méthode de mise à jour pour chacun, qui fonctionne très bien. La liste est de type std::list<EngineComponent> et est appelée engineComponents.

void Game::Update() 
{ 
    for (EngineComponent component: this->engineComponents) 
    { 
     component.Update(); 
    } 
} 

J'ai aussi une sous-classe de EngineComponent appelé DrawableEngineComponent.

La question apparaît lorsque je tente de faire une itération similaire:

void Game::Draw() 
{ 
    for (DrawableEngineComponent component: this->engineComponents) 
    { 
     component.Draw(); 
    } 
} 

Ce produit l'erreur « Aucun de conversion définie par l'utilisateur approprié de « EngineComponent » à « DrawableEngineComponent » existe ». Étant donné que cette implémentation était très bien et en diète en C#, je ne suis pas vraiment sûr de la meilleure façon de résoudre ce problème en C++.

Je peux penser à d'autres moyens qui devraient/devraient fonctionner, mais je me demande s'il existe des fonctionnalités en C++ pour faire cela d'une manière similaire à C# sans avoir à définir manuellement une conversion.

Les définitions pour les deux classes concernées sont les suivantes:

class EngineComponent 
{ 
public: 
    EngineComponent(void); 
    ~EngineComponent(void); 

    virtual void Update(void); 
}; 


class DrawableEngineComponent : public EngineComponent 
{ 
public: 
    DrawableEngineComponent(void); 
    ~DrawableEngineComponent(void); 

    virtual void Draw(void); 
}; 

Et oui, je copie le cadre XNA un peu;)

+1

Cette tentative d'incantation crie un problème potentiel de tranchage. , et vous pouvez faire bien faire [lire cette question et les réponses] (http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c/274634#274634) pour en savoir plus à ce sujet – WhozCraig

+1

Vous dites que cela fonctionne en C#, mais je ne suis pas sûr que ce soit vrai :-) En C#, que se passe-t-il si l'un des composants de la liste est * non * Drawable. Est-ce que ça plante? Ou passer cet élément dans la liste? Veuillez préciser quel comportement vous attendez/désirez à cet égard. –

+0

@AaronMcDaid Vous avez raison, ce n'est pas le cas. J'ai été un peu confus quand j'ai écrit ça. Il est très facile à faire en C# en vérifiant simplement chaque objet lorsqu'il apparaît dans la boucle (avec une ligne de code), c'est ce que j'aurais dû écrire. Bon endroit :) –

Répondre

2

La raison réelle vous obtenez cette erreur est que la manière vous avez défini la gamme en fonction de, vous récupérez des objets par copie plutôt que de référence:

for (EngineComponent component: this->engineComponents) 
{ 
    // component is a copy of the object in the list 
} 

EngineComponent est un super-classe et donc il n'y a pas d'i mplicit cast à une classe dérivée. Si vous essayez de copier un DrawableEngineComponent sur une liste de EngineComponent le compilateur n'a aucun moyen de savoir si l'objet source est vraiment une classe dérivée.

Les conteneurs standard ne gèrent pas vraiment très bien les objets polymorphes. Une meilleure solution consiste à utiliser std::shared_ptr pour stocker des pointeurs vers les objets.

std::list<std::shared_ptr<EngineComponent>> myList; 
myList.push_back(std::make_shared<DrawableEngineComponent>()); 

Cela envelopper un DrawableEngineComponent dans un pointeur partagé et le stocker dans la liste. Il est accessible d'une manière similaire à votre méthode originale:

for (auto& component: engineComponents) 
{ 
    component->Update(); 
} 

Mais cette fois-ci vous avez un objet entièrement polymorphes, vous pouvez appeler. Si l'objet surcharge la méthode Update() dans une sous-classe, c'est celle-ci qui sera appelée. Vous pouvez également utiliser la coulée pour obtenir un pointeur vers la sous-classe si c'est ce dont vous avez besoin:

for (auto& component: engineComponents) 
{ 
    auto pDrawComponent = dynamic_cast<DrawableEngineComponent*>(component.get()); 
    if (pDrawComponent) 
    { 
     // it's drawable 
    } 
} 
+0

+1 (J'attendais sur des épingles et des aiguilles pour que tu puisses enfin récupérer cette chose afin que je puisse voter up = P). – WhozCraig

+0

C'est exactement ce que je recherchais! Merci beaucoup! Cependant, je n'ai pas implémenté le 'std :: shared_ptr' et tout semble fonctionner correctement. Je ne suis pas sûr de ce que sont les avantages et les inconvénients de le faire, je vais devoir lire :) –

2

Le std::list<EngineComponent> est juste que; une liste d'objets de composants de moteur. Lorsque vous appuyez sur la liste, vous faites une copie de l'objet que vous appuyez. Sauf si vous avez défini une conversion entre la sous-classe et sa base, elle échouera. De même, essayer de convertir de la base à la sous-classe comme vous le faites échouera également. Ce que vous voulez probablement est une liste de pointeurs vers des objets de classe de base, c'est-à-dire: Un std::list<unique_ptr<EngineComponent>> ferait probablement l'affaire.Quel que soit le type de pointeur que vous utilisez, vous devrez downcaster à DrawableEngineComponent avant de pouvoir appeler la méthode Draw:

 
for (unique_ptr<EngineComponent> & engCompPtr: engineComponents) 
{ 
    DrawableEngineComponent & drawableEngComp = dynamic_cast<DrawableEngineComponent &>(*engCompPtr); 
    drawableEngComp.Draw(); 
} 

Je ne sais pas beaucoup de choses sur C#, mais je suppose que lorsque vous travaillez avec des objets , il est effectivement mis en œuvre par des pointeurs intelligents d'une certaine description.

1

std::list<EngineComponent> stocke un groupe de EngineComponents. Si vous ajoutez un DrawableEngineComponent à la liste, vous tranchez la partie « drawable » hors de celui-ci:

std::list<EngineComponent> engineComponents; 
EngineComponent comp1; 
engineComponents.push_back(comp1); // no problem, just copies it into the list 
DrawableEngineComponent comp2; 
EngineComponent newComp2 = *(EngineComponent*)(&comp2); // the drawable part is now sliced out! 
engineComponents.push_back(newComp2); // this adds the sliced version to the list 

Plus que probablement, vous êtes désireux de stocker une liste de pointeurs vers EngineComponents; mais même avec cela, il serait conseillé de stocker les tirables séparément (sinon vous devrez faire un casting et vérifier pour vous assurer qu'il peut être casté à ce type.)