2010-07-13 19 views
18

È valido? Una pratica accettabile?Eliminazione di un riferimento

typedef vector<int> intArray; 

intArray& createArray() 
{ 
    intArray *arr = new intArray(10000, 0); 

    return(*arr); 
} 


int main(int argc, char *argv[]) 
{ 

    intArray& array = createArray(); 

    //.......... 

    delete &array; 

    return 0; 
} 
+2

valido? si, accettabile? oh dio no! perché? Perché qualcuno dovrebbe farlo? ho bisogno di sapere! – gnzlbg

+0

@gnzlbg Presumibilmente perché questo "assicura" che il valore di ritorno _cannot_ sia "nullo" (anche se vedi il punto di Billy ONeal sulla sicurezza delle eccezioni). –

+0

Se il vettore deve trovarsi nello heap: utilizzare un puntatore intelligente che gestisce automaticamente la deallocazione. Si noti inoltre che il valore restituito non può essere nullo perché la versione di nuovo che viene utilizzata funziona o getta. In ogni caso, si tratta di una progettazione API molto scadente, il dover deallocare la memoria tramite un riferimento (non proprietario) non è una pratica comune. E poiché in pratica funzionano effettivamente riferimenti null, ti fideresti di chiunque abbia scritto questa API per non restituire un riferimento null? Non lo farei – gnzlbg

risposta

51

Il comportamento del codice sarà il comportamento previsto. Ora, il problema è che mentre si potrebbe pensare che la programmazione riguardi la scrittura di qualcosa da elaborare per il compilatore, è altrettanto importante scrivere qualcosa che altri programmatori (o voi in futuro) capiranno e saranno in grado di mantenere. Il codice che hai fornito in molti casi sarà equivalente all'uso di puntatori per il compilatore, ma per gli altri programmatori sarà solo una potenziale fonte di errori.

I riferimenti sono intesi come alias di oggetti gestiti altrove, in qualche modo. In generale le persone saranno sorprese quando incontreranno lo delete &ref e nella maggior parte dei casi i programmatori non si aspetteranno di dover eseguire un delete sull'indirizzo di un riferimento, quindi è probabile che in futuro qualcuno chiamerà la funzione e dimenticherà di eliminare e avrai una perdita di memoria.

Nella maggior parte dei casi, la memoria può essere gestita meglio mediante l'uso di puntatori intelligenti (se non è possibile utilizzare altri costrutti di alto livello come std::vector s). Nascondendo il puntatore dietro il riferimento, rendi più difficile l'uso di puntatori intelligenti sul riferimento restituito, e quindi non stai aiutando, ma rendi più difficile per gli utenti lavorare con la tua interfaccia.

Infine, la cosa buona dei riferimenti è che quando li leggi in codice tu rispondi allo allo che la vita dell'oggetto è gestita da qualche altra parte e non devi preoccuparti di ciò. Usando un riferimento invece di un puntatore si sta sostanzialmente tornando alla singola soluzione (in precedenza solo in C puntatori) e improvvisamente si deve prestare particolare attenzione a tutti i riferimenti per capire se la memoria debba essere gestita lì o no, piuttosto che un maggiore sforzo , più tempo per pensare alla gestione della memoria e meno tempo per preoccuparsi del problema che si sta risolvendo - con la tensione in più del codice insolito, le persone si abituano a cercare perdite di memoria con i puntatori e non si aspettano nessuno dei riferimenti.

In poche parole: la memoria trattenuta dal riferimento nasconde all'utente la necessità di gestire la memoria e rende più difficile farlo correttamente.

12

È valido ... ma non vedo perché tu voglia mai farlo. Non è un'eccezione sicura, e std::vector gestirà comunque la memoria per te. Perché new è?

MODIFICA: Se si restituisce nuova memoria da una funzione, è necessario restituire il puntatore, per evitare che gli utenti delle teste della propria funzione esplodano.

+0

Come è possibile che questo non sia sicuro per le eccezioni? Il chiamante non è altro che la funzione 'main()', giusto? – Tanz87

+1

@Tanz: Nel caso specifico di main() sei "sicuro" perché sai che qualsiasi eccezione non gestita in main sarà non gestita (risultante in 'termninate()' nel caso in cui venga lanciata un'eccezione). Tuttavia (1) dovresti ancora usare gli oggetti per gestire le risorse, anche in main, per coerenza se non altro, e (2) la domanda dell'OP è generale, non specifica per main(). –

12

Sì, penso che funzionerà. Ma se avessi visto qualcosa di simile in qualsiasi codice su cui avessi lavorato, lo avrei sbrogliato e sistemato subito.

Se si desidera restituire un oggetto assegnato, utilizzare un puntatore. Per favore!

+1

Probabilmente, potrebbero volerli racchiudere in un puntatore intelligente appropriato, per renderlo sicuro rispetto alle eccezioni. –

+0

@Steven Sudit, il puntatore intelligente appropriato sarebbe 'auto_ptr': http://stackoverflow.com/questions/631633/returning-a-pointer-which-is-required-to-be-held-by-a-smart -pointer –

+0

Sì, questo è certamente il valore predefinito ragionevole. Non volevo essere troppo specifico perché non sono presente su tutti i puntatori intelligenti non ANSI, come quelli in Boost. –

0

Funzionerà ma temo che sia una pratica inaccettabile. C'è una forte convenzione nel mondo C++ che la gestione della memoria è fatta con i puntatori. Il tuo codice viola questa convenzione ed è suscettibile di far inciampare praticamente su chiunque lo usi.

Sembra che si stia tentando di evitare di restituire un puntatore non elaborato da questa funzione. Se la tua preoccupazione è dover controllare ripetutamente un puntatore valido in main, puoi usare un riferimento per l'elaborazione dell'array. Ma createArray restituisce un puntatore e assicuratevi che il codice che cancella la matrice lo prenda anche come puntatore.Oppure, se è davvero così semplice, dichiara semplicemente la matrice sullo stack principale e rinuncia completamente alla funzione. (Il codice di inizializzazione in quel caso potrebbe prendere un riferimento all'oggetto array da inizializzare e il chiamante potrebbe passare il suo oggetto stack al codice init.)

3

È valido?

Sì.

Una pratica accettabile?

No.

Questo codice ha diversi problemi:

  • La linea guida di progettare per il comportamento meno sorprendente è rotto: si torna qualcosa che "assomiglia" un oggetto, ma deve essere cancellato dal codice client (che dovrebbe significare un puntatore - un riferimento dovrebbe essere qualcosa che punta sempre a un oggetto valido).

  • l'allocazione può fallire. Anche se controlli il risultato nella funzione di allocazione, cosa restituirai? Un riferimento non valido? Ti affidi all'assegnazione di un'eccezione per un caso del genere?

Come principio di progettazione, prendere in considerazione sia la creazione di un oggetto RAII che è responsabile per la gestione del ciclo di vita del vostro oggetto (in questo caso un puntatore intelligente) o eliminando il puntatore allo stesso livello di astrazione che è stato creato:

typedef vector<int> intArray; 

intArray& createArray() 
{ 
    intArray *arr = new intArray(10000, 0); 

    return(*arr); 
} 

void deleteArray(intArray& object) 
{ 
    delete &object; 
} 

int main(int argc, char *argv[]) 
{ 
    intArray& array = createArray(); 
    //.......... 
    deleteArray(array); 
    return 0; 
} 

Questo design migliora la codifica stile consistenza (allocazione e deallocazione sono nascosti e attuate allo stesso livello di astrazione), ma sarebbe ancora più sensato lavorare tramite un puntatore di un riferimento (a meno che il fatto che l'oggetto è assegnato dinamicamente deve rimanere un attrezzo dettaglio delle azioni per alcuni motivi di progettazione).

+0

Perché un errore di allocazione è un problema? Se l'allocazione fallisce la funzione _returns_ un'eccezione (quella versione dell'operatore new throw). – gnzlbg

0

È valido perché il compilatore può essere compilato ed eseguito correttamente. Tuttavia, questo tipo di pratiche di codifica rende codici più difficile per i lettori e manutentori a causa della

  • gestione manuale della memoria
  • trasferimento della proprietà Vague al lato client

Ma c'è un punto sottile, in questa domanda , è un requisito di efficienza. A volte non è possibile restituire il valore di accesso perché la dimensione dell'oggetto potrebbe essere troppo grande, ingombrante come in questo esempio (1000 * sizeof (int)); Per tale motivo; dovremmo usare i puntatori se abbiamo bisogno di trasferire oggetti a parti diverse del nostro codice. Ma questo non significa che l'implementazione è accettabile perché per questo tipo di requisiti c'è uno strumento molto utile, è smart-pointers. Quindi, la decisione di progettazione spetta al programmatore, ma per questo tipo di dettagli di implementazione specifici, il programmatore dovrebbe utilizzare modelli accettabili come i puntatori intelligenti in questo esempio.