Question

J'ai passé les 4 dernières années en C #, donc je suis intéressé par les pratiques exemplaires et modèles de conception commune actuelle en C ++. Prenons l'exemple partiel suivant:

class World
{
public:
    void Add(Object *object);
    void Remove(Object *object);
    void Update();
}

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this;
        }
    }
}

Ici, nous avons un monde responsable de la gestion d'un ensemble d'objets et de les mettre à jour régulièrement. Le feu est un objet qui pourrait être ajouté au monde dans de nombreuses circonstances différentes mais généralement par un autre objet déjà dans le monde. Le feu est le seul objet qui sait quand il a brûlé si actuellement je l'ai même supprimer. L'objet qui a créé le feu ne risque existe plus ou pertinent.

Est-ce une chose sensée à faire ou est-il une meilleure conception qui pourrait aider à réparer ces objets?

Était-ce utile?

La solution

Le problème est que vous créez vraiment un couplage implicite entre l'objet et la classe mondiale.

Si je tente d'appeler Update () en dehors de la classe mondiale, ce qui se passe? Je pourrais finir avec l'objet supprimé, et je ne sais pas pourquoi. Il semble que les responsabilités sont mal mélangés. Cela va causer des problèmes au moment où vous utilisez la classe d'incendie dans une nouvelle situation que vous ne l'aviez pas pensé quand vous avez écrit ce code. Qu'advient-il si l'objet doit être supprimé de plus d'un endroit? Peut-être qu'il devrait être retiré aussi bien du monde, la carte actuelle, et l'inventaire du joueur? Votre fonction de mise à jour va retirer du monde, puis supprimez l'objet, et la prochaine fois que la carte ou l'inventaire tente d'accéder à l'objet, de mauvaises choses arrivent.

En général, je dirais qu'il est très peu intuitive pour une fonction de mise à jour () pour supprimer l'objet qu'elle est mise à jour. Je dirais aussi qu'il est unintuitive pour un objet à se supprimer. L'objet devrait plus probablement une sorte de moyen de déclencher un événement en disant qu'il a fini de brûler, et toute personne intéressée peut maintenant agir sur ce point. Par exemple en le retirant du monde. Pour le supprimer, penser en termes de propriété.

Qui est propriétaire de l'objet? Le monde? Cela signifie que le monde seul arrive à décider quand l'objet meurt. C'est bien aussi longtemps que va durer plus longtemps une autre référence à elle la référence du monde à l'objet. Pensez-vous que l'objet possède lui-même? Qu'est ce que ça veut dire? L'objet doit être supprimé lorsque l'objet n'existe plus? N'a pas de sens.

Mais s'il n'y a pas de propriétaire unique clairement défini, mettre en œuvre la propriété partagée, par exemple en utilisant un pointeur intelligent mise en œuvre de comptage de référence, tels que boost::shared_ptr

Mais ayant une fonction de membre sur l'objet lui-même, qui est codé en dur pour supprimer l'objet de un spécifique, si elle existe ou pas là, et si oui ou non il existe aussi dans une autre liste et également supprimer l'objet lui-même, quel que soit des références à ce qu'il existe, est une mauvaise idée.

Autres conseils

Vous avez Update une fonction virtuelle, ce qui suggère que les classes dérivées peuvent passer outre la mise en œuvre de Update. Cela introduit deux grands risques.

1.) Une mise en œuvre surchargée peut se rappeler de faire un World.Remove, mais peut oublier le delete this. La mémoire est une fuite.

2.) La mise en œuvre surchargée appelle la Update classe de base, ce qui fait un delete this, mais procède alors à plus de travail, mais avec un pointeur non valide ce-.

Prenons cet exemple:

class WizardsFire: public Fire
{
public:
    virtual void Update()
    {
        Fire::Update();  // May delete the this-object!
        RegenerateMana(this.ManaCost); // this is now invaild!  GPF!
    }
}

Il n'y a rien vraiment mal avec les objets eux-mêmes supprimer en C ++, la plupart des implémentations de comptage de référence utiliseront quelque chose de similaire. Cependant, il est regardé comme une technique un peu ésotérique, et vous aurez besoin de garder un œil très proche sur les questions de propriété.

La suppression échouera et peut se bloquer le programme si l'objet était pas alloué et construit avec de nouveaux. Cette construction vous empêchera de instancier la classe sur la pile ou statiquement. Dans votre cas, vous semblez utiliser une sorte de système d'allocation. Si vous vous en tenez à cela, cela pourrait être en sécurité. Je ne le ferai pas, cependant.

Je préfère un modèle de propriété plus stricte. Le monde devrait posséder les objets et être responsable de les nettoyer. Ou bien ont remove monde faire ou ont mise à jour (ou une autre fonction) renvoient une valeur qui indique qu'un objet doit être supprimé.

Je suppose aussi que dans ce type de modèle, vous allez vouloir faire référence à compter vos objets pour éviter de se retrouver avec des pointeurs ballants.

Il fonctionne certainement pour les objets créés par new et lorsque l'appelant de Update est correctement informé de ce comportement. Mais je l'éviter. Dans votre cas, la propriété est clairement au monde, donc je rendre le monde le supprimer. L'objet ne se crée pas, je pense qu'il devrait aussi ne pas se supprimer. Peut être très surprenant si vous appelez une fonction « mise à jour » sur votre objet, mais tout d'un coup cet objet n'existant plus sans faire du monde quoi que ce soit sur lui-même (en dehors de suppression hors de sa liste - mais dans un autre cadre Le code d'appel Mise à jour sur l'objet ne remarquera pas que).

Quelques idées sur ce

  1. Ajouter une liste de références d'objets à World. Chaque objet dans cette liste est en attente pour le retrait. Ceci est une technique courante, et est utilisé dans wxWidgets pour les fenêtres de premier niveau qui ont été fermées, mais peut encore recevoir des messages. Dans le temps d'inactivité, lorsque tous les messages sont traités, la liste est en attente de traitement, et les objets sont supprimés. Je crois que Qt suit une technique similaire.
  2. Dites au monde que l'objet veut supprimer. Le monde sera informé correctement et prendre soin de toute substance qui doit être fait. Quelque chose comme un deleteObject(Object&) peut-être.
  3. Ajoutez une fonction shouldBeDeleted à chaque objet qui retourne true si l'objet souhaite être supprimé par son propriétaire.

Je préférerais l'option 3. Le monde appellerait à jour. Et après cela, il semble que l'objet doit être supprimé, et peut le faire -. Ou si elle le souhaite, il se souvient de ce fait en ajoutant cet objet à une liste en attente de suppression manuelle-

Il est une douleur dans le cul quand vous ne pouvez pas être sûr quand et quand ne pas pouvoir accéder à des fonctions et des données de l'objet. Par exemple, dans wxWidgets, il y a une classe wxThread qui peut fonctionner en deux modes. L'un de ces modes (appelé « détachable ») est que si ses principales déclarations de fonction (et les ressources de fil doivent être libérés), il se supprime (pour libérer la mémoire occupée par l'objet wxThread) au lieu d'attendre que le propriétaire du fil objet d'appeler une attente ou se joindre à la fonction. Cependant, cela provoque des maux de tête graves. Vous ne pouvez jamais appeler toutes les fonctions sur elle parce qu'elle aurait pu être résilié à toutes les circonstances, et vous ne pouvez pas avoir créé pas nouveau. Pas mal de gens me dit qu'ils n'aiment beaucoup que le comportement de celui-ci.

L'auto suppression de l'objet de référence est compté une odeur, imho. Comparons:

// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances. 
class Bitmap {
    ~Bitmap() {
        if(bmp->dec_refcount() == 0) {
            // count drops to zero => remove 
            // ref-counted object. 
            delete bmp;
        }
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount();
    int inc_refcount();

};

Comparez cela avec des objets refcounted auto-suppression:

class Bitmap {
    ~Bitmap() {
        bmp->dec_refcount();
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount() {
        int newCount = --count;
        if(newCount == 0) {
            delete this;
        }
        return newCount;
    }
    int inc_refcount();
};

Je pense que le premier est tellement mieux, et je crois que des objets comptés de référence bien conçus font pas ne « Supprimer cette », car elle augmente le couplage: La classe en utilisant la référence données de comptage a savoir et rappelez-vous que les données au sujet lui-même supprime comme un effet secondaire de décrémenter le compte de référence. Notez comment « bmp » devient peut-être un pointeur ballants dans destructor de ~ Bitmap. On peut dire, ne pas le faire « supprimer ce » est beaucoup plus agréable ici.

Réponse à une question similaire "Quelle est l'utilisation de supprimer cette "

C'est une application de comptage de référence assez commun et je l'ai utilisé avec succès avant que.

Cependant, je l'ai aussi vu planter. Je voudrais rappeler les circonstances de l'accident. @abelenky est un endroit que je l'ai vu planter.

Il aurait pu être encore où vous Fire de sous-classe, mais ne parviennent pas à créer un destructor virtuel (voir ci-dessous). Si vous ne disposez pas d'un destructeur virtuel, la fonction Update() appellera ~Fire() au lieu du ~Flame() destructor approprié.

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this; //Make sure this is a virtual destructor.
        }
    }
};

class Flame: public Fire
{
private: 
    int somevariable;
};

D'autres ont mentionné des problèmes avec « supprimer cela. » Bref, étant donné que « World » gère la création de feu, il doit également gérer sa suppression.

Un autre problème est que si le monde se termine par un feu encore brûlant. Si cela est le seul mécanisme par lequel un incendie peut être détruit, vous pourriez finir avec un feu orphelin ou de déchets.

Pour la sémantique que vous voulez, je serais un drapeau « actif » ou « vivant » dans votre classe « Fire » (ou le cas échéant l'objet). Alors le monde vérifiait à l'occasion de son inventaire des objets et se débarrasser de ceux qui ne sont plus actifs.

-

Une note plus est que votre code comme écrit a le feu héritant privé de l'objet, puisque c'est la valeur par défaut, même si elle est beaucoup moins fréquente que inheiritance public. Vous devriez probablement faire le genre d'héritage explicite, et je suppose que vous voulez vraiment inheritiance public.

Il est généralement pas une bonne idée de faire un « supprimer cette » sauf si nécessaire ou utilisé d'une manière très simple. Dans votre cas, il semble que le monde peut tout simplement supprimer l'objet lorsqu'il est retiré, à moins que d'autres dépendances nous ne voyons pas (dans ce cas, l'appel de suppression provoque des erreurs car ils ne sont pas notifiés). Garder la propriété en dehors de votre objet permet à votre objet à mieux encapsulées et plus stable. L'objet ne deviendra pas invalide à un moment tout simplement parce qu'il a choisi de se supprimer

Je ne ce qu'il ya quelque chose d'intrinsèquement mauvais avec un objet lui-même la suppression, mais une autre méthode possible serait d'avoir le monde est responsable de la suppression des objets dans le cadre de :: Supprimer (en supposant que tous les objets ont été supprimés également supprimés.)

supprimer ce qui est très risqué. Il n'y a rien « mal » avec elle. Il est légal. Mais il y a tellement de choses qui peuvent mal tourner lorsque vous l'utilisez qu'il doit être utilisé avec parcimonie.

Prenons le cas où une personne a des pointeurs ouverts ou des références à l'objet, puis il va et se supprime.

Dans la plupart des cas, je pense que la durée de vie de l'objet devrait être géré par le même objet qui le crée. Il est tout simplement plus facile de savoir où la destruction a lieu.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top