2013-03-06 10 views
5

Mi piacerebbe assicurarmi che nessuno sia in grado di cancellare alcun oggetto dalla mia gerarchia di classi, altrimenti usando un metodo di distruzione fornito.Come posso proteggere i distruttori per un'intera gerarchia di classi in modo gestibile?

La logica è che qualsiasi oggetto da questa gerarchia deve prendere uno speciale mutex di scrittura prima che inizi a distruggere se stesso per assicurarsi che gli oggetti non vengano cancellati mentre un altro thread li sta usando.

So che potrei evitare questo problema con il conteggio dei riferimenti, ma sarebbe un cambiamento molto più grande del sistema anche in termini di potenziale impatto sulle prestazioni e allocazione della memoria.

C'è un modo per rendere in modo efficiente/intelligente tutti i distruttori protetti in modo che le classi figlio possano chiamare i loro genitori distruttori mentre gli estranei devono utilizzare Distruggi?

Una soluzione che è sicura (cioè non marcisce) che ho inventato è quella di rendere privati ​​tutti i distruttori e dichiarare ogni classe derivata come un amico della classe base ma preferirei qualcosa di più elegante, meno manuale e più facile da mantenere (come non richiedere di modificare le classi base per derivarne).

È disponibile qualcosa di simile? Forse qualche trucco intelligente che rende le cose "funzionanti" come vorrei?

ps. La soluzione che ho scelto per ora NON è quella di impedire a chiunque di chiamare delete in tutti i casi (l'ho appena reso protetto nella classe base), ma rilevare questa situazione e chiamare l'abort nel distruttore della classe base.

+5

Aspetta un secondo, cosa c'è di sbagliato con limitandosi a dichiarare i distruttori come 'protected'? –

+0

@AndyProwl Presumo il problema è che in tal caso, una classe derivata male può rendere pubblico il suo distruttore. – Angew

+0

Esattamente - potrebbe anche essere un errore onesto commesso da qualcuno che mantiene il codice in un paio d'anni - preferirei non lasciare dietro di sé delle trappole :) – RnR

risposta

1

Non tentare di reinventare i meccanismi di durata forniti dalla lingua.

Affinché un oggetto della classe sia inizializzato correttamente, deve essere in grado di ripulirsi.

Nel suo costruttore passa il mutex, o un mezzo per ottenere un mutex, che può usare nel suo distruttore.

+0

Scusa ma il distruttore della classe base è chiamato "alla fine" della catena di distruzione - questo significa che dovrei ricordare di aggiungere qualcosa a OGNI distruttore di classe derivato e non è qualcosa che voglio (perché ancora una volta una classe derivata può dimenticare di fallo così ecc.) – RnR

+0

Mi dispiace, non capisco. Perché non incapsulare le cose che devono fare attenzione quando puliscono? Che cosa pensi della tua funzione 'Destroy', che la mia soluzione non può fare? –

+0

La funzione Destroy prenderà prima il mutex e poi procederà a chiamare delete sull'oggetto. La tua soluzione (a meno che non sia implementata in ogni classe derivata) prima distruggerà gli oggetti fino a raggiungere il distruttore della classe base e quindi prenderà il mutex (quindi l'oggetto sarà già danneggiato/semidistrutto). – RnR

0

Può essere fatto con l'aiuto di test. Per una classe con un distruttore protetta avete bisogno di 2 casi di test:

  1. una funzione (in un unico file) che non riesce a compilare semplicemente la creazione di un tale oggetto
  2. una funzione (in seconda file) che crea un oggetto con una classe derivata che compila.

Se entrambi i test funzionano, penso che si possa essere sicuri che le classi siano protette come si desidera.

Non so se sei in grado di implementarlo con il tuo sistema di build ma ho un esempio utilizzando bjam (da boost) a git hub. Il codice è semplice e funziona per gcc e msvc. Se non conosci bjam dovresti cercare in Jamroot.jam. Penso che sia chiaro senza ulteriori commenti come funziona questo semplice esempio.

+0

Il problema qui è che con le classi derivate non è richiesto che i loro distruttori siano più protetti. – RnR

1

Grazie per tutti i vostri commenti e discussioni. Sì, ha dimostrato che è impossibile fare quella che sarebbe la mia prima scelta naturale :((avere la "protezione" del distruttore ha effetto nelle classi derivate proprio come fa la "virtualità").

La mia soluzione in questo caso particolare (risolvere tutti i potenziali problemi con l'essere in grado di commettere errori di pietra introducendo nuove classi derivate che violano gli accordi precedenti e mantenendo la soluzione in un posto/manutenibile (nessuna duplicazione del codice nelle classi derivate, ecc.)) è:

  1. fare tutti i distruttori gerarchia di classi esistenti posso trovare protette
  2. fornire un metodo nella classe base che può essere utilizzato per avviare la distruzione di questi oggetti Destroy - se chiamato il metodo si accende una bandiera sull'oggetto distrutto che è stato correttamente distrutto e quindi chiama Elimina su di esso
  3. Nel distruttore della classe base (una volta che ci arriviamo) controlla il flag - se non è impostato significa che qualcuno ha introdotto una nuova classe e ha chiamato delete direttamente o evitato i controlli di protezione del compilatore in qualche altro modo (abusato di amicizie, ecc. - Annullo l'applicazione in questo caso per assicurarmi che il problema non possa essere ignorato/perso
1

Ho avuto gli stessi bisogni, ma per un motivo diverso. Nella nostra struttura aziendale, quasi tutte le classi derivano da una classe comune BaseObject. Questo oggetto utilizza un numero di riferimento per determinarne la durata. BaseObject ha in particolare questi tre metodi: retain(), release() e autorelease(), fortemente ispirato dalla lingua Objective-C. L'operatore delete viene chiamato solo all'interno di release() quando il conteggio di ritenzione raggiunge . Nessuno dovrebbe chiamare direttamente delete, ed è anche indesiderabile avere istanze BaseObject nello stack.

Pertanto, tutti i nostri distruttori dovrebbero essere protected o private. Per far rispettare questo, come so che è impossibile dal linguaggio, ho scritto uno script Perl che cerca tutti i distruttori all'interno di una directory di origine e crea un report. È quindi relativamente facile verificare che la regola sia rispettata.

ho fatto il pubblico sceneggiatura, disponibile qui: https://gist.github.com/prapin/308a7f333d6836780fd5

+0

Grazie. Questo è un approccio interessante e concordo sul fatto che può aiutare in particolare nei progetti più piccoli, dove è possibile gestirlo realisticamente e cercare le classi a cui è interessato sapendo che non ci sono altri luoghi in cui avrebbe bisogno di guardare. Una cosa interessante che potrebbe essere utile sarebbe quella di stampare l'intero albero ereditario piuttosto che la classe base in quanto permetterebbe di aggiungere facilmente controlli e assicurarsi che tutte le classi derivate da una data classe di base forniscano una data protezione ecc. – RnR

Problemi correlati