Domanda

Ho passato gli ultimi 4 anni in C #, quindi sono interessato a corrente buone pratiche e modelli di progettazione comune in C ++. Si consideri il seguente esempio parziale:

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;
        }
    }
}

Qui abbiamo un mondo incaricato di gestire un insieme di oggetti e li aggiorna regolarmente. Il fuoco è un un oggetto che potrebbe essere aggiunto al mondo in molte circostanze diverse, ma in genere già nel mondo un altro oggetto. Il fuoco è l'unico oggetto che sa quando è bruciata così Attualmente ho essa stessa l'eliminazione. L'oggetto che ha creato il fuoco è probabile che non esiste più o pertinenti.

E 'una cosa sensata da fare o c'è un progetto migliore che avrebbe aiutato a ripulire questi oggetti?

È stato utile?

Soluzione

Il problema di questo è che si sta davvero creando un accoppiamento implicita tra l'oggetto e la classe mondiale.

Se provo a chiamare Update () al di fuori della classe mondiale, che cosa succede? Potrei finire con l'oggetto che viene eliminato, e non so perché. Sembra che le responsabilità sono mal mischiati. Questo sta per causare problemi nel momento in cui utilizza la classe di fuoco in una situazione nuova che non aveva pensato quando hai scritto questo codice. Cosa succede se l'oggetto deve essere eliminato da più di un luogo? Forse dovrebbe essere rimosso sia dal mondo, la mappa corrente, e l'inventario del giocatore? La funzione di aggiornamento lo rimuoverà dal mondo, e quindi eliminare l'oggetto, e la prossima volta che la mappa o l'inventario tenta di accedere all'oggetto, Bad Things Happen.

In generale, direi che è molto intuitivo per una funzione Update () per eliminare l'oggetto si sta aggiornando. Vorrei anche dire che è poco intuitivo per un oggetto da eliminare se stesso. L'oggetto dovrebbe più probabile avere una sorta di modo per generare un evento dicendo che ha finito di bruciare, e chiunque sia interessato può ora agire su questo. Per esempio rimuovendo dal mondo. Per eliminarlo, pensare in termini di proprietà.

A chi appartiene l'oggetto? Il mondo? Ciò significa che il mondo da sola arriva a decidere quando l'oggetto muore. Questo va bene finché di riferimento a livello mondiale per l'oggetto sta per sopravvivere un altri riferimenti ad esso. Pensi che l'oggetto stesso possiede? Che cosa vuol dire, anche? L'oggetto deve essere eliminato quando l'oggetto non esiste più? Non ha senso.

Ma se non c'è singolo proprietario ben definito, implementare comproprietà, ad esempio utilizzando un puntatore intelligente attuazione conteggio di riferimento, come boost::shared_ptr

Ma avere una funzione membro sull'oggetto stesso, che è codificato per rimuovere l'oggetto da una elenco specifico, anche se non esiste lì, e se non esiste anche in qualsiasi altra lista , e anche eliminare l'oggetto in sé, indipendentemente da quale esistono riferimenti ad esso, è una cattiva idea.

Altri suggerimenti

Hai fatto Update una funzione virtuale, suggerendo che le classi derivate possono sovrascrivere l'attuazione di Update. Questo introduce due grossi rischi.

1.) Un'implementazione override può ricordarsi di fare un World.Remove, ma può dimenticare il delete this. La memoria è trapelato.

2.) L'attuazione override chiama il Update classe base, che fa un delete this, ma poi procede con più lavoro, ma con un valido questo puntatore.

Si consideri questo esempio:

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

Non c'è davvero niente di sbagliato con l'eliminazione di oggetti stessi in C ++, la maggior parte delle implementazioni di conteggio dei riferimenti utilizzeranno qualcosa di simile. Tuttavia, si è guardato come una tecnica un po 'esoterica, e sarà necessario tenere un occhio molto vicino alle questioni legate alla proprietà.

L'eliminazione fallirà e può mandare in crash il programma se l'oggetto era non allocato e costruito con il nuovo. Questo costrutto ti impedirà di istanziare la classe nello stack o staticamente. Nel tuo caso, in cui sembra essere utilizzando qualche tipo di piano di ripartizione. Se ti infili a ciò, questo potrebbe essere al sicuro. Io di certo non lo farei, però.

Io preferisco un modello di proprietà più rigoroso. Il mondo dovrebbe possedere gli oggetti in esso ed essere responsabile della loro pulizia. O hanno del mondo sopprimere farlo o hanno l'aggiornamento (o un'altra funzione) restituisce un valore che indica che un oggetto deve essere eliminato.

Sono anche indovinando che in questo tipo di modello, si sta andando a voler fare riferimento a contare gli oggetti per evitare di finire con i puntatori penzolanti.

E 'certamente funziona per gli oggetti creati dai new e quando il chiamante di Update è adeguatamente informato su questo comportamento. Ma vorrei evitarlo. Nel tuo caso, la proprietà è chiaramente al mondo, quindi vorrei rendere il mondo eliminarlo. L'oggetto non crea se stesso, penso che non dovrebbe anche eliminare se stesso. Può essere molto sorprendente se si chiama una funzione "Aggiorna" sul vostro oggetto, ma poi improvvisamente l'oggetto non è più esistente senza Mondiale fare nulla fuori di sé (a parte La rimozione dal ruolo - ma in un altro frame codice chiamante Aggiornamento sul oggetto non noterà che).

Alcune idee su questo

  1. Aggiungi un elenco di riferimenti a oggetti a World. Ogni oggetto in tale elenco è in attesa per la rimozione. Questa è una tecnica comune, ed è utilizzato in wxWidgets per finestre principali che sono stati chiusi, ma può comunque ricevere i messaggi. In tempo di inattività, quando tutti i messaggi vengono elaborati, la lista in attesa vengono elaborati, e gli oggetti vengono cancellati. Credo Qt segue una tecnica simile.
  2. dire al mondo che l'oggetto vuole essere cancellato. Il mondo sarà adeguatamente informato e si prenderà cura di tutte le cose che deve essere fatto. Qualcosa di simile a un deleteObject(Object&) forse.
  3. Aggiungi una funzione shouldBeDeleted a ciascun oggetto che restituisce true se l'oggetto vuole essere cancellato dal suo proprietario.

Io preferirei l'opzione 3. Il mondo avrebbe chiamato Update. E dopo che, sembra se l'oggetto deve essere eliminato, e può farlo -. O se lo desidera, si ricorda questo fatto con l'aggiunta di quell'oggetto a un elenco di rimozione in attesa manualmente

E 'una rottura di scatole quando non si può essere sicuri quando e quando non essere in grado di accedere alle funzioni e ai dati dell'oggetto. Ad esempio, in wxWidgets, c'è una classe wxThread che può funzionare in due modalità. Uno di questi modi (chiamato "staccabile") è che se i suoi principali restituisce la funzione (e le risorse di thread dovrebbero essere liberati), si elimina (per liberare la memoria occupata dall'oggetto wxThread) invece di aspettare che il proprietario del thread opporsi a chiamare un'attesa o una funzione aderire. Tuttavia, questo provoca forte mal di testa. Non si può mai chiamare qualsiasi funzione su di esso, perché avrebbe potuto essere risolto in qualsiasi circostanza, e non si può avere, non creato con il nuovo. Un po 'di persone mi hanno detto che non piace molto che il comportamento di esso.

Il sé soppressione oggetto contato riferimento odora, imho. Mettiamo a confronto:

// 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();

};

Confronto che con oggetti refcounted auto-eliminazione:

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();
};

Credo che il primo è molto più bello, e credo oggetti contati di riferimento ben progettato fanno non do "cancella questo", perché aumenta l'accoppiamento: La classe utilizzando il riferimento contato dati deve sapere e ricordare su che i dati si elimina come effetto collaterale della decrementando il conteggio dei riferimenti. Si noti come "bmp" diventa forse un puntatore penzoloni nel distruttore di ~ bitmap. Probabilmente, non fare che "cancella questo" è molto più bello qui.

Risposta a una simile domanda "Qual è l'uso di eliminare questo "

Questa è un'implementazione abbastanza comune conteggio dei riferimenti e ho usato che con successo prima.

Tuttavia, ho anche visto in crash. Vorrei poter ricordare le circostanze per l'incidente. @abelenky è un posto che ho visto crash.

Potrebbe essere stato in cui è ulteriormente sottoclasse Fire, ma non riescono a creare un distruttore virtuale (vedi sotto). Quando non si dispone di un distruttore virtuale, la funzione Update() chiamerà ~Fire() al posto del ~Flame() distruttore appropriata.

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;
};

Altri hanno menzionato problemi con "cancellare questo." In breve, dal momento che "World" gestisce la creazione del Fuoco, si deve anche gestire la sua eliminazione.

Un altro problema è se il mondo finisce con un fuoco brucia ancora. Se questo è l'unico meccanismo attraverso il quale un incendio può essere distrutto, allora si potrebbe finire con un fuoco orfano o spazzatura.

Per la semantica che si desidera, avrei una bandiera "attivo" o "vivo" nella tua classe "Fire" (o oggetto, se del caso). Allora il mondo sarebbe in occasione controllare il suo inventario di oggetti, e di sbarazzarsi di quelli che non sono più attivi.

-

Una nota di più è che il codice scritto come ha Fuoco ereditando privatamente da Object, dal momento che è il default, anche se è molto meno comune inheiritance pubblico. Probabilmente si dovrebbe fare il tipo di eredità esplicita, e ho il sospetto che si vuole veramente inheritiance pubblico.

Non è generalmente una buona idea di fare un "cancellare questo" se non strettamente necessario o utilizzato in un modo molto semplice. Nel tuo caso sembra che il mondo può solo eliminare l'oggetto quando viene rimosso, a meno che non ci sono altre dipendenze che non stiamo vedendo (nel qual caso la chiamata di eliminazione causerà errori in quanto non notificati). Mantenendo la proprietà al di fuori l'oggetto permette l'oggetto di essere meglio incapsulato e più stabile:. L'oggetto non sarà più valido ad un certo punto, semplicemente perché ha scelto di cancellare se stesso

Non faccio questo ci sia qualcosa di intrinsecamente sbagliato con l'eliminazione di un oggetto in sé, ma un altro metodo possibile sarebbe quello di avere il mondo sia responsabile per l'eliminazione di oggetti come parte di :: Rimuovere (supponendo che tutti gli oggetti rimossi sono stati cancellati.)

Elimina questo è molto rischioso. Non c'è nulla di "sbagliato" con esso. E 'legale. Ma ci sono tante cose che possono andare male quando lo si utilizza che dovrebbe essere usato con parsimonia.

Si consideri il caso in cui qualcuno ha puntatori aperti o riferimenti all'oggetto, e poi si va e si cancella.

Nella maggior parte dei casi, penso che la durata oggetto deve essere gestita dallo stesso oggetto che crea. Si rende solo più facile sapere dove si verifica la distruzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top