2010-09-27 4 views
0

En C++ pour Windows, j'ai une fabrique d'objets censée créer une série d'objets Info en passant un pointeur sur l'objet à une fonction Create et en retournant un objet créé. AbstractInfo est une classe de base dont nous disposons de nombreux types d'objets Info.Retour d'un pointeur d'argument sur un objet

Je pensais que je pouvais créer un objet Information comme suit:

MyInfoObject* InfoObj = NULL; // derived from AbstractInfo object 
InfoFactory fc; 

fc.CreateInfoObject(&InfoObj); // Now I want to get my initialized pointer back 

Mais il dit qu'il ne peut pas faire le casting ... Quel est le problème?

ERREUR: ne peut pas lancer de MyInfoObject ** _ W64 à AbstractInfo **

EDIT: La première réponse indique que l'interface est horrible, ne peut pas voir qui est l'allocation etc ... Comment puis-je améliorer?

Répondre

8

Réfléchissons à une mise en œuvre possible de CreateInfoObject:

void InfoFactory::CreateInfoObject(AbstractInfo** info) 
{ 
    *info = new SuperInfo; 
} 

Maintenant, SuperInfo et MyInfoObject n'ont rien en droit commun?

C'est pourquoi, en général, ce qui suit est interdit:

struct Base {}; 
struct D1: Base {}; 
struct D2: Base {}; 

int main(int argc, char* argv[]) 
{ 
    Base** base = nullptr; 
    D1* d = nullptr; 
    base = d; 
} 

Comme il permettrait D1 de pointer vers quelque chose sans rapport.

Il existe plusieurs solutions:

// 1. Simple 
AbstractInfo* info = nullptr; 
fc.CreateInfoObject(info); 

// 2. Better interface 
std::unique_ptr<AbstractInfo> info = fc.CreateInfoObject(); 

Ensuite, si vous savez avec certitude que vous avez, en fait, un MyInfoObject vous pouvez utiliser:

MyInfoObject* myInfo = static_cast<MyInfoObject*>(info); 

ou si vous n'êtes pas sûr:

MyInfoObject* myInfo = dynamic_cast<MyInfoObject*>(info); 

qui va définir myInfo à nullptr si jamais le info n'a pas indiqué une instance de MyInfoObject (ou dérivé).

Gardez à l'esprit cependant, que votre interface est vraiment horrible. C'est très C-ish et on ne sait pas si la mémoire est effectivement allouée ou non ... et qui est responsable de la gérer si c'est le cas.

EDIT:

En bon style C++, nous utilisons à la fois RAII indiquent la propriété et assurer le nettoyage nécessaire. RAII est bien connu mais pas très indicatif, je préfère moi-même la nouvelle SBRM (Scope Bound Resources Management).

L'idée est qu'au lieu d'utiliser un pointeur nu, qui ne donne pas d'indication sur la propriété (c.-à-vous devez appeler supprimer sur elle?) Vous devez utiliser un pointeur intelligent , comme par exemple unique_ptr.

Vous pouvez également utiliser le paramètre return de la méthode, pour éviter un processus d'initialisation en deux étapes (créez d'abord le pointeur, puis faites-le pointer vers un objet). Voici un exemple concis:

typedef std::unique_ptr<AbstractInfo> AbstractInfoPtr; 

// Note: if you know it returns a MyInfoObject 
// you might as well return std::unique_ptr<MyInfoObject> 
AbstractInfoPtr InfoFactory::CreateInfoObject() 
{ 
    return AbstractInfoPtr(new MyInfoObject()); 
} 

// Usage: 
int main(int argc, char* argv[]) 
{ 
    InfoFactory factory; 
    AbstractInfoPtr info = factory.CreateInfoObject(); 

    // do something 

} // info goes out of scope, calling `delete` on its pointee 

Ici, il n'y a aucune ambiguïté quant à la propriété.

, notez aussi que vous comprenez mieux votre question ici:

std::unique_ptr<MyInfoObject> info = factory.CreateInfoObject(); 

ne compilerait pas parce que vous ne pouvez pas convertir un AbstractInfo* à un MyInfoObject* sans utiliser static_cast ou dynamic_cast.

+0

L'édition parle de votre commentaire! –

+1

@Tony: J'ai modifié ma réponse avec une proposition d'interface alternative. –

+0

Merci beaucoup pour l'explication. Est-ce que je peux maintenant faire une distribution descendante de mon AbstractInfoPtr récupéré à un objet dérivé ?? est-ce une bonne idée? –

2

Un pointeur vers un pointeur n'est tout simplement pas aussi flexible qu'un pointeur vers un objet. Le compilateur appliquera strictement le type sans tenir compte de l'arbre d'héritage.

La meilleure façon de corriger ce code est avec une double mission:

MyInfoObject* InfoObj = NULL; // derived from AbstractInfo object 
AbstractInfo* temp = NULL; 
InfoFactory fc; 

fc.CreateInfoObject(&temp); 
InfoObj = dynamic_cast<MyInfoObject*>(temp); 
+0

Le problème existerait même s'il renvoyait un objet sans utiliser de pointeurs (par exemple avec une méthode 'AbstractInfo InfoFactory :: CreateInfoObject();'.) Il essaie de transformer une classe abstraite en sous-classe concrète, ce qui produit un programme-formé et ne compilera pas. –

+0

@Jonathan, ce problème est exactement pourquoi j'utilise 'dynamic_cast' dans la réponse. Si l'objet renvoyé n'est pas réellement un objet MyInfoObject, la distribution échouera. –

3

Parce que CreateInfoObject() prend un pointeur à un à pointer-un AbstractInfo, il est possible que la fonction renvoie une instance de AbstractInfo qui est pas une instance de MyInfoObject. Donc, vous pouvez vous retrouver avec un pointeur vers MyInfoObject que fait points à un DifferentInfoObject.

Remplacez le MyInfoObject *InfoObj par AbstractInfo *InfoObj et cela devrait fonctionner. Ne rejetez pas la conversion avec autre chose que dynamic_cast<> car vous ne savez pas avec certitude que CreateInfoObject() renvoie une instance de cette sous-classe.

3

Le compilateur vous a indiqué ce qui ne va pas. Vous ne pouvez pas convertir un pointeur de type T * en un pointeur de type U * lorsque T et U n'ont rien à faire l'un avec l'autre. Ici, T = MyInfoObject *, U = AbstractInfo * et ce sont deux types de pointeurs distincts qui ne partagent aucune relation d'héritage.

+1

Le problème n'est pas des pointeurs. Le problème est que 'CreateInfoObject()' pourrait renvoyer une instance de 'AbstractInfo' qui n'est * pas * aussi une instance de' MyInfoObject' et le code appelant n'a aucun moyen de s'assurer que cela ne puisse pas arriver. –

+0

Pour renforcer cette réponse: même si les classes MyInfoObject et AbstractInfo ont une relation d'héritage, les types de pointeur MyInfoObject * et AbstractInfo * ne le sont pas. –

+1

@Jonathan: Le code appelant est invalide de toute façon. Il est invalide parce que T et U ne sont pas liés à la référence. Le compilateur ne vous permet pas de faire quelque chose comme ça à moins que vous ne lanciez explicitement les pointeurs, auquel cas vous pourriez rencontrer le problème auquel vous faites référence. – sellibitze

0

Considérons que cela se passe dans CreateInfoObject.

permet de dire il y a une autre sous-classe de AbstractInfo, appelez Foo.

À l'intérieur CreateInfoObject nous créons un nouveau Foo et l'assignons à *info. (Prévision autorisée).

Mais à l'extérieur nous avons maintenant Foo à l'intérieur d'un MyInfoObject** ce qui est faux.

Questions connexes